Summary

(speed, total distance, and end-to-end distance) for cell lines across 7 experimental conditions

Key

  • pson:
  • brca:
  • _motil: ~ motility data
  • _expr: ~ expression data

Setup

Load packages, log into Synapse, etc.

# for getting data from Synapse
library(synapser)

# general data management and manipulation packages
library(tidyverse)
library(stringr)
library(janitor)
library(forcats)

# plotting and visualization packages
library(ggthemes)
library(ggrepel)
library(ggExtra)
library(patchwork)
library(kableExtra)

# analysis packages
library(broom)
library(annotables)
synLogin()

Data collection

All data was retrievied from the PS-ON Cell Line Characterization Portal in Synapse.

Expression data

I’ll first grab basic “sample” info for cell lines from the Proteomics and RNA Synapse table, for all files containing RSEM gene quantification outputs.

# collect "sample" metadata for cell lines with expression data available 
prot_rna_meta_fv_id <- "syn10320914"
sample_df <- synTableQuery(
  str_glue(
    "SELECT replace(name, '_rsem.genes.results.txt', '') as sample, id,
     diagnosis, cellType, catalogNumber, cellLine, 
     experimentalCondition, organ, tumorType 
     FROM {fv_id} 
     WHERE study = 'RNA Study' AND name LIKE '%rsem%'",
    fv_id = prot_rna_meta_fv_id
  ),
  includeRowIdAndRowVersion = FALSE
) %>% 
  as.data.frame()

Taking a look at sample_df:

sample_df

I’ll do a bit of munging with the experimentalCondition variable to to create separate variables for surface, laminate, and stiffness (in case we want to break things down further with any visualization or analysis).

sample_tidy_df <- sample_df %>% 
  # tidy up experimental condition info
  mutate(experimentalCondition = str_replace_all(
    experimentalCondition, " Acid", "Acid"
  )) %>%
  separate(experimentalCondition,
           into = c("stiffness","stiffness_units", "surface"),
           remove = FALSE, extra = "merge", fill = "left") %>%
  mutate(
    surface = case_when(
      surface %in% c("Collagen", "Fibronectin") ~ experimentalCondition,
      TRUE ~ surface
    ),
    stiffness_units = ifelse(is.na(stiffness), NA, stiffness_units)
  ) %>%
  separate(surface, into = c("surface", "laminate")) %>%
  mutate(
    stiffness = parse_number(stiffness),
    stiffness_units = str_trim(stiffness_units),
    stiffness_norm = case_when(
      stiffness_units == "Pa" ~ stiffness / 1000,
      TRUE ~ stiffness
    )
  )

I also split stiffness_units into a separate variable and added a stiffness_norm such that all values are in “kPa” units. Note: scroll over to view the new columns.

sample_tidy_df

Next, for all the samples in sample_tidy_df, I’ll download the file corresponding to the Synapse ID, extract TPM values, and combine everything into a single dataframe. Even though I’ll do some additional transformation and filtering with the expression data below, I’ll go ahead and save the full dataframe as "pson_expr_tpm_df.RData".

# extract expression data from individual files
extract_expr_tpm <- function(syn_id) {
  synGet(syn_id) %>% 
    pluck("path") %>% 
    read_tsv() %>% 
    select(gene_id, TPM)
}
sample_id_map <- sample_tidy_df %>% 
  select(sample, id) %>% 
  deframe()
pson_expr_tpm_file <- "../data/pson_expr_tpm_df.RData"
if (!fs::file_exists(pson_expr_tpm_file)) {
  # collect expression data for all cell lines and conditions
  pson_expr_tpm_df <- sample_tidy_df %>% 
    pluck("id") %>% 
    set_names() %>% 
    map_df(extract_expr_tpm, .id = "id") %>% 
    spread(id, TPM) %>% 
    rename(!!!sample_id_map)
  save(pson_expr_tpm_df, file = pson_expr_tpm_file)
} else {
  load(pson_expr_tpm_file)
}

Columns of the pson_expr_tpm_df dataframe correspond to sample observations in the sample_tidy_df dataframe; each row contains the TPM expression value of the gene indicated by the gene_id column. Looking at the first few rows only (there are 56632 total…):

head(pson_expr_tpm_df)

Motility data

I’ll collect metadata for motility summary files from the Physical Characterization View Synapse table (again, for all cell lines). I only need the id, catalogNumber, and experimentalCondition columns so that I (i) can download each file below and (ii) correctly match up sample information from the expression data.

# retrieve cell line motility metadata from Synapse file view
motil_meta_fv_id <- "syn7747734"
pson_motil_meta_df <- synTableQuery(
  str_glue("SELECT id, catalogNumber, experimentalCondition FROM {fv_id} 
            WHERE study = 'Motility' AND name LIKE '%summary%'", 
           fv_id = motil_meta_fv_id),
  includeRowIdAndRowVersion = FALSE
) %>% 
  as.data.frame() %>% 
  # format experimental condition to match sample dataframe above
  mutate(experimentalCondition = str_replace_all(
    experimentalCondition, " Acid", "Acid"
  ))

Here’s what the metadata dataframe looks like (note: only 9 of the 29 cell lines have corresponding expression data for the motility conditions tested).

pson_motil_meta_df

 

Aside: format and save sample data

I’ll go ahead and add Synapse IDs for the motility summary files to sample_tidy_df. The resulting sample_map_df dataframe contains all sample information and file IDs for both expression and motility files (id_expr and id_motil, respectively). I’ll also create a color palette that I can use for indicating diagnosis (cancer type) in plots below. I’ll save both the sample_map_df dataframe and diagnosis_colors list as "pson_sample_map_and_colors.RData".

# build and save master sample dataframe
sample_map_df <- sample_tidy_df %>% 
  left_join(pson_motil_meta_df, 
            by = c("catalogNumber", "experimentalCondition"),
            suffix = c("_expr", "_motil"))
diagnosis_colors <- sample_map_df %>% 
  distinct(diagnosis) %>% 
  mutate(color = ggthemes::tableau_color_pal("colorblind10")(nrow(.))) %>% 
  deframe()
save(sample_map_df, file = "../data/pson_sample_map_and_colors.RData")

Here’s what the final sample info dataframe looks like:

sample_map_df

And here are our diagnosis colors:

diagnosis_colors
   Colon Cancer  Not Applicable     Skin Cancer    Brain Cancer Prostate Cancer   Breast Cancer 
      "#006BA4"       "#FF800E"       "#ABABAB"       "#595959"       "#5F9ED1"       "#C85200" 

Similar to the expression data above, I’ll download the motility summary files for all cell lines and all experimental conditions, read in the data, format things a bit, and combine into a single dataframe. I’ll save the resulting dataframe as "pson_motil_summary_df.RData". Note: I’ll append columns from sample_map_df to the motility summary dataframe to avoid additional join steps below (the dataframe is still relatively small).

# extract motility summary data from individual files
extract_motil_summary <- function(syn_id) {
  if (!is.na(syn_id)) {
    synGet(syn_id) %>% 
      pluck("path") %>% 
      # only take the first 4 rows; anything after that is unstructured and might
      # cause errors
      read_tsv(n_max = 4) %>% 
      # clean and standardize columns and row names based on readme info 
      # ftp://caftpd.nci.nih.gov/psondcc/PhysicalCharacterization/Motility/README.txt
      clean_names() %>% 
      mutate(statistic = c(
        "average_value", 
        "total_number_of_cells_tracked" ,
        "standard_deviation", 
        "standard_error"
      )) %>% 
      gather(summary_metric, value, -statistic) %>% 
      spread(statistic, value)
  } else {
    data.frame()
  }
}
pson_motil_summary_file <- "../data/pson_motility_summary_df.RData"
if (!fs::file_exists(pson_motil_summary_file)) {
  # collect summary data for all cell lines and conditions
  pson_motil_summary_df <- sample_map_df %>% 
    pluck("id_motil") %>% 
    set_names() %>% 
    map_df(extract_motil_summary, .id = "id_motil") %>% 
    left_join(
      sample_map_df,
      by = "id_motil"
    )
  save(pson_motil_summary_df, file = pson_motil_summary_file)
} else {
  load(pson_motil_summary_file)
}

Data formatting

Now that I have the basic information downloaded and stored in relatively usable formats, I’ll do a bit of tidying and minimal “feature engineering” to faciliate visualization and analysis below.

Expression data

For the expression data, I’ll just convert the dataframe of TPM values into a matrix and save the file to share (providing an alternative format for anyone who wants to use it). I could also normalize the data (i.e., log transform), but I’ll save that for the analysis steps.

Save expression matrix file

pson_expr_tpm_mat_file <- "../data/pson_expr_tpm_mat.RData"
if (!fs::file_exists(pson_expr_tpm_mat_file)) {
  pson_expr_tpm_mat <- pson_expr_tpm_df %>% 
    column_to_rownames("gene_id") %>% 
    as.matrix()
  save(pson_expr_tpm_mat, file = pson_expr_tpm_mat_file)
} else {
  load(pson_expr_tpm_mat_file)
}

Create and save gene info dataframe

I’ll use the annotables package to augment the Ensembl gene IDs with some additional field to provide more context:

  • entrez: NCBI Entrez gene ID
  • symbol: HGNC gene symbol
  • biotype: Protein coding, pseudogene, mitochondrial tRNA, etc.
  • description: Full gene name/description

I’ll save this dataframe as "pson_expr_gene_df.RData".

gene_df <- pson_expr_tpm_df %>% 
  select(gene_id) %>% 
  left_join(grch38, by = c("gene_id" = "ensgene")) %>% 
  select(gene_id, entrez, symbol, biotype, description)
save(gene_df, file = "../data/pson_expr_gene_info.RData")

Taking a look at the first few rows of gene_df:

head(gene_df)

Motility data

I happen to know from some earlier inspection of the motility data that scales for average values are fairly different across cell lines. I’ll keep the original values in the dataframe, but add centered and scaled versions (using the scale() function). I’ll save the augmented dataframe as "pson_motility_tidy_df.RData" and use this for analysis below.

scale_this <- function(x) as.vector(scale(x))
pson_motil_tidy_file <- "../data/pson_motility_tidy_df.RData"
if (!fs::file_exists(pson_motil_tidy_file)) {
  # scale and center motility measurement data
  pson_motil_tidy_df <- pson_motil_summary_df %>%
    group_by(cellLine, summary_metric) %>% 
    mutate(average_value_scaled = scale_this(average_value)) %>%
    ungroup()
  save(pson_motil_tidy_df, file = pson_motil_tidy_file)
} else {
  load(pson_motil_tidy_file)
}

Exploratory data analysis

Checking out trends and distributions for motility and expression data.

Cell line motility overview

I’ll do a bit of formatting and arranging to control the order in which cell lines and cancer types are plotted (to hopefully make trends easier to see).

# format motility data for plotting (set up factors for axis ordering, etc.)
plot_df <- pson_motil_tidy_df %>%
  group_by(diagnosis, cellLine) %>% 
  mutate(
    cv_scaled_value = sd(average_value_scaled) / mean(average_value_scaled)
  ) %>% 
  ungroup() %>% 
  arrange(desc(cv_scaled_value)) %>% 
  replace_na(list(stiffness_norm = "NA", laminate = "NA")) %>% 
  mutate(diagnosis = fct_inorder(diagnosis),
         diagnosis = fct_relevel(diagnosis, "Not Applicable", after = Inf)) %>% 
  arrange(as.integer(diagnosis)) %>% 
  mutate(cellLine = fct_inorder(cellLine))

As a quick sanity check, I’ll verify that I can reproduce the plot I previously made in the motility_brca.R script:

plot_df %>% 
  filter(diagnosis %in% c("Breast Cancer")) %>%
  ggplot(aes(x = cellLine, y = average_value)) +
  geom_col(aes(fill = laminate, alpha = stiffness_norm), 
           position = position_dodge(0.9)) +
  geom_errorbar(
    aes(ymin = average_value - standard_error, 
        ymax = average_value + standard_error, 
        color = laminate, 
        alpha = stiffness_norm), 
    size = 0.5, width = 0.25, position = position_dodge(0.9)
  ) +
  scale_alpha_manual("stiffness [kPa]", values = c(0.3, 1, 1)) +
  scale_color_manual(values = c("black", "black", "black")) +
  scale_fill_brewer(palette = "Set1") +
  facet_grid(summary_metric ~ surface, scales = "free_y") +
  labs(title = "Average motility measures vs. surface") +
  xlab("cell line") +
  ylab("") +
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Average values

While I don’t have a strong justification, I’ve decided up front that I’m not interested in the “end-to-end distance” (end_to_end_distance_um) summary measure for motility. With that, I still have 2 summary metrics for 9 cell lines, categorized into 6 diagnoses / cancer types, across 7 conditions. That’s a lot of information to capture in one plot but I’ll try to work with heatmaps. The patchwork package comes in handy here for combining panels with different color palettes.

p_speed <- plot_df %>% 
  filter(summary_metric == "speed_um_hr") %>% 
  ggplot(aes(x = cellLine, y = experimentalCondition)) + 
  geom_tile(aes(fill = average_value), colour = "white", size = 0.2) + 
  scale_fill_viridis_c("Average speed [um/hr]") + 
  guides(fill = guide_colorbar(direction = "horizontal", 
                               title.position = "top")) +
  xlab("") + 
  ylab("") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        legend.title = element_text(size = 10, face = "bold"),
        strip.text.x = element_blank(),
        strip.text.y = element_blank()) +
  facet_grid(surface ~ summary_metric, scales = "free_y", space = "free_y")
p_dist <- plot_df %>% 
  filter(summary_metric == "total_distance_um") %>% 
  ggplot(aes(x = cellLine, y = experimentalCondition)) + 
  geom_tile(aes(fill = average_value), colour = "white", size = 0.2) + 
  scale_fill_viridis_c("Average distance [um]", option = 3) + 
  guides(fill = guide_colorbar(direction = "horizontal", 
                               title.position = "top")) +
  xlab("") + 
  ylab("") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        legend.title = element_text(size = 10, face = "bold"),
        strip.text.x = element_blank(),
        strip.text.y = element_blank(),
        plot.margin = margin(t = -10)) +
  facet_grid(surface ~ summary_metric, scales = "free_y", space = "free_y")
p_diag <- plot_df %>% 
  filter(summary_metric == "speed_um_hr") %>% 
  ggplot(aes(x = cellLine, y = 1)) + 
  geom_tile(aes(fill = diagnosis), colour = "white", size = 0.2) + 
  scale_fill_manual(values = diagnosis_colors) +
  ylab("") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        legend.margin = margin(t = 80),
        legend.title = element_text(size = 10, face = "bold"),
        legend.key.size = ggplot2::unit(8, "pt"),
        legend.text = element_text(size = 8),
        strip.text.x = element_blank(),
        plot.margin = margin(t = -5, b = 10)) +
  facet_grid(. ~ summary_metric, scales = "free_y", space = "free_y")
p_comb <- p_speed + p_dist + p_diag + 
  plot_layout(ncol = 1, heights = c(20, 20, 2.5))
p_comb

Scaled and centered

As expected, the differing scales of motility metrics across conditions for each cell line make it tough to discern any patterns. I’ll try again with the centered and scaled average values. Note: this is admittedly a lot of duplicated code to produce a plot with only minor changes in content.

p_diag <- plot_df %>% 
  filter(summary_metric == "speed_um_hr") %>% 
  ggplot(aes(x = cellLine, y = 1)) + 
  geom_tile(aes(fill = diagnosis), colour = "white", size = 0.2) + 
  scale_fill_manual(values = diagnosis_colors) +
  scale_x_discrete(position = "top") + 
  xlab("") + 
  ylab("") +
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5),
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank(),
        legend.margin = margin(t = -60),
        legend.title = element_text(size = 10, face = "bold"),
        legend.key.size = ggplot2::unit(8, "pt"),
        legend.text = element_text(size = 8),
        strip.text.x = element_blank(),
        plot.margin = margin(t = -5, b = 10)) +
  facet_grid(. ~ summary_metric, scales = "free_y", space = "free_y")
p_speed <- plot_df %>% 
  filter(summary_metric == "speed_um_hr") %>% 
  ggplot(aes(x = cellLine, y = experimentalCondition)) + 
  geom_tile(aes(fill = average_value_scaled), colour = "white", size = 0.2) + 
  scale_fill_viridis_c("Average speed* [um/hr]") + 
  guides(fill = guide_colorbar(direction = "horizontal", 
                               title.position = "top")) +
  xlab("") + 
  ylab("") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        legend.title = element_text(size = 10, face = "bold"),
        strip.text.x = element_blank(),
        strip.text.y = element_blank()) +
  facet_grid(surface ~ summary_metric, scales = "free_y", space = "free_y")
p_dist <- plot_df %>% 
  filter(summary_metric == "total_distance_um") %>% 
  ggplot(aes(x = cellLine, y = experimentalCondition)) + 
  geom_tile(aes(fill = average_value_scaled), colour = "white", size = 0.2) + 
  scale_fill_viridis_c("Average distance* [um]", option = 3) + 
  guides(fill = guide_colorbar(direction = "horizontal", 
                               title.position = "top")) +
  xlab("") + 
  ylab("") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        legend.title = element_text(size = 10, face = "bold"),
        strip.text.x = element_blank(),
        strip.text.y = element_blank(),
        plot.margin = margin(b = 10)) +
  facet_grid(surface ~ summary_metric, scales = "free_y", space = "free_y")
p_comb <- p_diag + p_speed + p_dist + 
  plot_layout(ncol = 1, heights = c(2.5, 20, 20)) +
  labs(caption = "* centered and scaled")
p_comb

Still nothing as obvious or pronounced as I might have hoped. For example, none of the conditions seem to be universally associated with increased (or decreased) motility relative to the others — even “glass” is the lowest value for some cell lines and the highest for others. I guess the takeaway is that motility regulation and response is cell-type specific — though there might be common drivers on the molecular level.

Motility vs. condition

Here’s a different way to look at possible trends of cell line motility with respect to experimental condition:

pson_motil_tidy_df %>% 
  filter(summary_metric != "end_to_end_distance_um") %>% 
  mutate(experimentalCondition = fct_rev(experimentalCondition),
         experimentalCondition = fct_relevel(experimentalCondition, "Glass")) %>% 
  ggplot(aes(x = experimentalCondition, y = average_value)) + 
  geom_point(aes(colour = diagnosis)) +
  geom_line(aes(colour = diagnosis, group = cellLine)) +
  ggrepel::geom_label_repel(
    data = pson_motil_tidy_df %>%  
      filter(experimentalCondition == "30 kPa polyacrylamide Collagen",
             summary_metric != "end_to_end_distance_um"),
    aes(label = cellLine),
    size = 3, nudge_y = 5, alpha = 0.7, label.padding = unit(0.1, "lines")
  ) +
  scale_colour_manual(values = diagnosis_colors) + 
  guides(colour = FALSE) +
  facet_grid(summary_metric ~ diagnosis, scales = "free") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 8))

?geom_label_repel

Distributions

This is a quick check to see how motility metric values are distributed across conditions for each cell line. I’m looking for some semblance of normality, though the sample size is fairly small. I’ll likely check associations with both parameteric and non-parametric methods below, just to be safe.

pson_motil_tidy_df %>% 
  ggplot(aes(x = average_value)) +
  stat_density(aes(colour = cellLine),
               geom = "line", position = "identity") +
  scale_color_brewer(palette = "Reds") +
  facet_wrap(~ summary_metric, scales = "free")

Expression overview

Before analyzing the expression data, I’d like to apply some basic transformations: log-normalizing the TPM expression values and removing/filtering low, rarely, or “flatly” (~ low variance) expressed genes.

expr_filter_df <- pson_expr_tpm_df %>% 
  gather(sample, tpm, -gene_id) %>% 
  mutate(log_tpm = log(tpm + 1)) %>% 
  group_by(gene_id) %>% 
  summarize(n_expr = sum(log_tpm > 0), 
            avg_expr = mean(log_tpm),
            # formula for ~CV for log-normalized data, according to Wikipedia
            cv_expr = sqrt(exp(sd(log_tpm) ^ 2) - 1)) %>% 
  ungroup() %>% 
  mutate(rare = n_expr < max(n_expr) / 2,
         low = avg_expr < 1,
         flat = cv_expr < 1,
         rare_or_low_or_flat = rare | low | flat)
expr_filter_df %>% 
  ggplot(aes(x = n_expr, y = avg_expr)) +
  geom_point(aes(colour = rare_or_low_or_flat), alpha = 0.5) +
  scale_color_colorblind()

Note: I’ll write a simple function for this step, so I can apply the same filtering for subsets of the data.

log_and_filter_genes <- function(expr_tpm_melted_df) {
  keep_genes <- expr_tpm_melted_df %>% 
    mutate(log_tpm = log(tpm + 1)) %>% 
    group_by(gene_id) %>% 
    summarize(n_expr = sum(log_tpm > 0), 
              avg_expr = mean(log_tpm),
              # formula for ~CV for log-normalized data, according to Wikipedia
              cv_expr = sqrt(exp(sd(log_tpm) ^ 2) - 1)) %>% 
    ungroup() %>% 
    mutate(rare = n_expr < max(n_expr) / 2,
           low = avg_expr < 1,
           flat = cv_expr < 1,
           rare_or_low_or_flat = rare | low | flat) %>% 
    filter(!rare_or_low_or_flat) %>% 
    pluck("gene_id")
    
    
    expr_tpm_melted_df %>% 
      filter(gene_id %in% keep_genes) %>% 
      mutate(logtpm = log(tpm + 1))
}
pson_expr_logtpm_df <- pson_expr_tpm_df %>% 
  gather(sample, tpm, -gene_id) %>% 
  log_and_filter_genes()

The subsetted expression dataframe pson_expr_logtpm_df contains 148932 genes. I can take a random sample (100 genes) again to check for ~normality.

set.seed(0)
pson_expr_logtpm_df %>% 
  group_by(gene_id) %>% 
  nest() %>% 
  sample_n(100) %>% 
  unnest() %>% 
  ggplot(aes(x = logtpm)) +
  stat_density(aes(group = gene_id), 
               geom = "line", position = "identity", alpha = 0.2)

Looks like a bunch of Guassian-ish curves to me, so that’s encouraging.


Motility-expression correlation

Below, I’ll take a look at correlation between gene expression and motility metrics in PS-ON cell lines from a few different perspectives:

  1. Across all cell lines and cancer types (diagnoses)
  2. Across all cell lines within each cancer type
  3. Across each breast cancer cell line

Across all cell lines

To ease some of the operations below, I’ll build a nested dataframe where, for each gene and summary metric, the data column stores a dataframe with expression values and average motility values across all cell lines and conditions.

nest_expr_motil_data <- function(expr_logtpm_df, facet = NULL) {
  groupvars <- c("gene_id", "summary_metric")
  joinvars <- c("sample")
  if (!is.null(facet)) {
    groupvars <- c(groupvars, facet)
    joinvars <- c(joinvars, facet)
  } else {
    facet <- ""
  }
  expr_logtpm_df %>% 
    left_join(
      pson_motil_tidy_df %>% 
        filter(!(summary_metric %in% "end_to_end_distance_um")) %>%
        select(sample, experimentalCondition, one_of(facet),
               summary_metric, average_value_scaled),
      by = joinvars
    ) %>% 
    filter(!is.na(experimentalCondition)) %>%
    select(gene_id, logtpm, summary_metric, average_value_scaled,
           experimentalCondition, one_of(facet)) %>%
    group_by(.dots = groupvars) %>%
    nest() %>%
    ungroup()
}

# build master dataframe for inspecting expression ~ motility trends
pson_expr_motil_df <- nest_expr_motil_data(pson_expr_logtpm_df)

Using the pson_expr_motil_df “master” dataframe, I can iterate across all genes to compute the correlation between expression and motility values across conditions. To clarify what this looks like for a single gene:

p1 <- pson_expr_motil_df %>% 
  slice(1) %>% 
  unnest(data) %>% 
  ggplot(aes(x = logtpm, y = average_value_scaled)) + 
  geom_point() +
  geom_smooth(method = "lm") +
  ylab("speed_um_hr")
p1 <- ggMarginal(p1)
grid::grid.newpage()
grid::grid.draw(p1)

calc_genewise_corr <- function(expr_motil_df) {
  expr_motil_corr_df <- expr_motil_df %>% 
    mutate(
      pearson = map(
        data, 
        ~ cor.test(.$logtpm, .$average_value_scaled, 
                   method = "pearson") %>% 
          tidy() %>% 
          dplyr::rename_all(.funs = ~str_c("pearson_", .))
      ),
      spearman = map(
        data, 
        ~ cor.test(.$logtpm, .$average_value_scaled, 
                   method = "spearman", exact = FALSE) %>% 
          tidy() %>% 
          dplyr::rename_all(.funs = ~str_c("spearman_", .))
      )
    ) %>% 
    select(-data)
  
  groupvars <- setdiff(
    names(expr_motil_corr_df), 
    c("gene_id", "pearson", "spearman")
  )
  print(groupvars)
  # # unnest and adjust for multiple testing
  expr_motil_corr_df %>%
    unnest(pearson) %>%
    unnest(spearman) %>% 
    group_by(.dots = groupvars) %>%
    mutate_at(.vars = vars(matches(".*_p.value")),
              .funs = funs(adj = p.adjust(., method = "BH"))) %>%
    ungroup()
}

# compute correlation across cell lines and conditions for all genes
expr_motil_corr_all_df <- calc_genewise_corr(pson_expr_motil_df)

Ideally, I’d like to find genes that are significantly correlated with motility after correcting for multiple hypothesis testing (i.e., Benjamini-Hochberg). Filtering for genes with adjusted p-value < 0.05, I get…

expr_motil_corr_all_sig_df <- expr_motil_corr_all_df %>% 
  filter_at(.vars = vars(matches(".*_p.value_adj")),
            .vars_predicate = any_vars(. < 0.05))
expr_motil_corr_all_sig_df

…nothing significant. Let’s try to look at unadjusted p-values.

expr_motil_corr_all_sig_df <- expr_motil_corr_all_df %>% 
  filter_at(.vars = vars(matches(".*_p.value")),
            .vars_predicate = any_vars(. < 0.05)) %>% 
  select(gene_id, summary_metric, 
         pearson = pearson_estimate, spearman = spearman_estimate) %>% 
  gather(corr_type, corr, pearson, spearman) %>% 
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
expr_motil_corr_all_sig_df

With this view, we get 5 genes that are correlated with either speed or distance based on either Pearson or Spearman correlation. Taking a look at these genes:

expr_motil_corr_all_sig_df %>% 
  mutate(neg = corr < 0,
         gene_name = if_else(is.na(gene_name), gene_id, gene_name)) %>% 
  arrange(corr) %>% 
  ggplot(aes(x = corr_type, y = corr)) +
  geom_point(aes(fill = corr), shape = 21, colour = "slategray", size = 3) +
  geom_text_repel(aes(label = gene_name), size = 3) +
  xlab("") +
  scale_fill_distiller(palette = "PuOr", limits = c(-1, 1)) +
  facet_grid(neg ~ summary_metric, scales = "free_y") +
  theme(strip.text.y = element_blank()) +
  labs(title = "Genes significantly* correlated with motility",
       subtitle = "(across all cell lines and cancer types)",
       caption = "* p-value < 0.05 (not adjusted for multiple testing)")

For each cancer type

Taking a slightly more granular approach, I’ll look at gene-motility correlation across cell lines for each cancer type.

# build master dataframe for inspecting expression ~ motility trends
pson_expr_motil_diag_df <- pson_expr_tpm_df %>%
  gather(sample, tpm, -gene_id) %>% 
  left_join(select(sample_map_df, sample, diagnosis), by = "sample") %>% 
  group_by(diagnosis) %>% 
  nest() %>% 
  mutate(data = map(data, log_and_filter_genes)) %>% 
  unnest(data) %>% 
  nest_expr_motil_data(facet = "diagnosis")

The principle here is pretty much the same, but with fewer points in each of the distributions that I’m comparing (at the same time, there’s less noise due to cell type specific effects, so we can generally expect to see correlation values with greater magnitude.)

p2 <- pson_expr_motil_diag_df %>% 
  slice(1) %>% 
  unnest(data) %>% 
  ggplot(aes(x = logtpm, y = average_value_scaled)) + 
  geom_point() +
  geom_smooth(method = "lm") +
  ylab("speed_um_hr")
p2 <- ggMarginal(p2)
grid::grid.newpage()
grid::grid.draw(p2)

# compute correlation across cell lines and conditions for all genes and all
# cancer types
expr_motil_corr_diag_df <- calc_genewise_corr(pson_expr_motil_diag_df)

Now, genes that are significantly correlated with motility after correcting for multiple hypothesis testing (i.e., BH-adjusted p-value):

expr_motil_corr_diag_sig_df <- expr_motil_corr_diag_df %>% 
  filter_at(.vars = vars(matches(".*_p.value_adj")),
            .vars_predicate = any_vars(. < 0.05)) %>% 
  select(gene_id, summary_metric, diagnosis,
         pearson = pearson_estimate, spearman = spearman_estimate) %>% 
  gather(corr_type, corr, pearson, spearman) %>% 
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
expr_motil_corr_diag_sig_df

An impressive 58 gene(s) correlated with either speed or distance based on either Pearson or Spearman correlation within any cancer type. Nobody likes multiple testing correction anyway — let’s skip it.

expr_motil_corr_diag_sig_df <- expr_motil_corr_diag_df %>% 
  filter_at(.vars = vars(matches(".*_p.value")),
            .vars_predicate = any_vars(. < 0.01)) %>% 
  select(gene_id, summary_metric, diagnosis,
         pearson = pearson_estimate, spearman = spearman_estimate) %>% 
  gather(corr_type, corr, pearson, spearman) %>% 
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
expr_motil_corr_diag_sig_df
expr_motil_corr_diag_sig_df %>% 
  ggplot(aes(x = gene_name, y = corr_type)) +
  geom_tile(aes(fill = corr), colour = "white", size = 0.5) +
  scale_fill_distiller(palette = "PuOr", limits = c(-1, 1)) +
  ylab("") +
  xlab("") +
  facet_grid(diagnosis ~ summary_metric, 
             scales = "free_x", space = "free_x", drop = TRUE) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 7),
        axis.text.y = element_text(size = 7),
        legend.position = "top",
        strip.text.y = element_text(angle = 0, size = 6)) +
  labs(title = "Genes significantly* correlated with motility",
       subtitle = "(across all cell lines within each cancer type)",
       caption = "* p-value < 0.01 (not adjusted for multiple testing)")

Across breast cancer cell lines

These are the same results as above, but focusing in on the genes that appear to be correlated with motility across breast cancer cell lines.

expr_motil_corr_diag_brca_sig_df <- expr_motil_corr_diag_df %>% 
  filter(diagnosis == "Breast Cancer") %>% 
  filter_at(.vars = vars(matches(".*_p.value")),
            .vars_predicate = any_vars(. < 0.05)) %>% 
  select(gene_id, summary_metric, diagnosis,
         pearson = pearson_estimate, spearman = spearman_estimate) %>%
  gather(corr_type, corr, pearson, spearman) %>%
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
expr_motil_corr_diag_brca_sig_df
expr_motil_corr_diag_brca_sig_df %>% 
  mutate(neg = corr < 0,
         gene_name = if_else(is.na(gene_name), gene_id, gene_name)) %>% 
  arrange(corr) %>% 
  ggplot(aes(x = corr_type, y = corr)) +
  geom_point(aes(fill = corr), shape = 21, colour = "slategray", size = 3) +
  geom_text_repel(aes(label = gene_name), size = 3) +
  xlab("") +
  scale_fill_distiller(palette = "PuOr", limits = c(-1, 1)) +
  facet_grid(neg ~ summary_metric, scales = "free_y") +
  theme(strip.text.y = element_blank()) +
  labs(title = "Genes significantly* correlated with motility",
       subtitle = "(across all breast cancer cell lines)",
       caption = "* p-value < 0.05 (not adjusted for multiple testing)")

For each breast cancer cell line

Taking a slightly more granular approach, I’ll look at gene-motility correlation across cell lines for each cancer type.

brca_expr_motil_cellline_df <- pson_expr_tpm_df %>%
  gather(sample, tpm, -gene_id) %>% 
  left_join(select(sample_map_df, sample, diagnosis, cellLine), 
            by = "sample") %>% 
  filter(diagnosis == "Breast Cancer") %>%
  group_by(cellLine) %>%
  nest() %>%
  mutate(data = map(data, log_and_filter_genes)) %>%
  unnest(data) %>%
  nest_expr_motil_data(facet = "cellLine")

Even fewer points in each of the distributions that I’m comparing (1 per experimental condition in both the motility and expression data) — higher likeliyhood of finding stronger correlation, but definitely less statistical power.

p3 <- brca_expr_motil_cellline_df %>% 
  slice(1) %>% 
  unnest(data) %>% 
  ggplot(aes(x = logtpm, y = average_value_scaled)) + 
  geom_point() +
  geom_smooth(method = "lm") +
  ylab("speed_um_hr")
p3 <- ggMarginal(p3)
grid::grid.newpage()
grid::grid.draw(p3)

# compute correlation across cell lines and conditions for all genes and all
# cancer types
brca_expr_motil_corr_cellline_df <- calc_genewise_corr(brca_expr_motil_cellline_df)

Any genes significantly correlated with motility after correcting for multiple hypothesis testing (BH-adjusted p-value)?

brca_expr_motil_corr_cellline_sig_df <- brca_expr_motil_corr_cellline_df %>% 
  filter_at(.vars = vars(matches(".*_p.value_adj")),
            .vars_predicate = any_vars(. < 0.05)) %>% 
  select(gene_id, summary_metric, cellLine,
         pearson = pearson_estimate, spearman = spearman_estimate) %>%
  gather(corr_type, corr, pearson, spearman) %>%
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
brca_expr_motil_corr_cellline_sig_df

Nope. Relaxing the threshold a bit further now (unadjusted p-value < 0.05):

brca_expr_motil_corr_cellline_sig_df <- brca_expr_motil_corr_cellline_df %>% 
  filter_at(.vars = vars(matches(".*_p.value")),
            .vars_predicate = any_vars(. < 0.05)) %>% 
  select(gene_id, summary_metric, cellLine,
         pearson = pearson_estimate, spearman = spearman_estimate) %>%
  gather(corr_type, corr, pearson, spearman) %>%
  left_join(select(gene_df, gene_name = symbol, everything()), by = "gene_id")
brca_expr_motil_corr_cellline_sig_df
brca_expr_motil_corr_cellline_sig_df %>% 
  mutate(neg = corr < 0,
         gene_name = if_else(is.na(gene_name), gene_id, gene_name)) %>% 
  ggplot(aes(x = gene_name, y = corr_type)) +
  geom_tile(aes(fill = corr), colour = "white", size = 0.5) +
  scale_fill_distiller(palette = "PuOr", limits = c(-1, 1)) +
  xlab("") +
  ylab("") +
  facet_grid(cellLine ~ summary_metric, 
             scales = "free_x", space = "free_x", drop = TRUE) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, size = 8),
        axis.text.y = element_text(size = 7),
        legend.position = "top",
        strip.text.y = element_text(angle = 0, size = 6)) +
  labs(title = "Genes significantly* correlated with motility",
       subtitle = "(for each breast cancer cell line)",
       caption = "* p-value < 0.05 (not adjusted for multiple testing)")

Not much to see, but maybe these genes mean more to others.


Session Info

sessionInfo()
R version 3.4.1 (2017-06-30)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS Sierra 10.12.6

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.4/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2          annotables_0.1.91     broom_0.4.3           kableExtra_0.7.0      patchwork_0.0.1      
 [6] ggExtra_0.8           ggrepel_0.7.0         ggthemes_3.4.0        janitor_1.0.0         forcats_0.2.0        
[11] stringr_1.3.0         dplyr_0.7.4           purrr_0.2.4           readr_1.1.1           tidyr_0.8.0          
[16] tibble_1.4.2          ggplot2_2.2.1.9000    tidyverse_1.2.1       synapser_0.1.25       rjson_0.2.15         
[21] PythonEmbedInR_0.1.11 R6_2.2.2             

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.16       lubridate_1.7.1    lattice_0.20-35    assertthat_0.2.0   rprojroot_1.2      digest_0.6.15     
 [7] psych_1.7.8        mime_0.5           cellranger_1.1.0   plyr_1.8.4         backports_1.1.1    evaluate_0.10.1   
[13] httr_1.3.1         pillar_1.2.1       rlang_0.2.0.9001   lazyeval_0.2.1     readxl_1.0.0       rstudioapi_0.7    
[19] miniUI_0.1.1       rmarkdown_1.8      labeling_0.3       foreign_0.8-69     munsell_0.4.3      shiny_1.0.5       
[25] compiler_3.4.1     httpuv_1.3.5       modelr_0.1.1       pkgconfig_2.0.1    pack_0.1-1         base64enc_0.1-3   
[31] mnormt_1.5-5       htmltools_0.3.6    tidyselect_0.2.4   codetools_0.2-15   viridisLite_0.3.0  crayon_1.3.4      
[37] withr_2.1.2        grid_3.4.1         nlme_3.1-131       jsonlite_1.5       xtable_1.8-2       gtable_0.2.0      
[43] magrittr_1.5       scales_0.5.0.9000  cli_1.0.0          stringi_1.1.6      reshape2_1.4.3     fs_1.1.0          
[49] xml2_1.1.1         RColorBrewer_1.1-2 tools_3.4.1        glue_1.2.0         hms_0.4.0          parallel_3.4.1    
[55] yaml_2.1.18        colorspace_1.3-2   rvest_0.3.2        knitr_1.20         bindr_0.1          haven_1.1.0       
LS0tCnRpdGxlOiAiUFNPTiBDZWxsIExpbmUgRGF0YSBFeHBsb3JhdGlvbiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCgojIyBTdW1tYXJ5Cgooc3BlZWQsIHRvdGFsIGRpc3RhbmNlLCBhbmQgZW5kLXRvLWVuZCBkaXN0YW5jZSkgZm9yIGNlbGwgbGluZXMgYWNyb3NzIDcgZXhwZXJpbWVudGFsIGNvbmRpdGlvbnMKCiMjIyBLZXkKCi0gYHBzb25gOgotIGBicmNhYDoKLSBgX21vdGlsYDogfiBtb3RpbGl0eSBkYXRhIAotIGBfZXhwcmA6IH4gZXhwcmVzc2lvbiBkYXRhCgotLS0KCiMjIFNldHVwCgpMb2FkIHBhY2thZ2VzLCBsb2cgaW50byBTeW5hcHNlLCBldGMuCgpgYGB7cn0KIyBmb3IgZ2V0dGluZyBkYXRhIGZyb20gU3luYXBzZQpsaWJyYXJ5KHN5bmFwc2VyKQoKIyBnZW5lcmFsIGRhdGEgbWFuYWdlbWVudCBhbmQgbWFuaXB1bGF0aW9uIHBhY2thZ2VzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShmb3JjYXRzKQoKIyBwbG90dGluZyBhbmQgdmlzdWFsaXphdGlvbiBwYWNrYWdlcwpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoZ2dFeHRyYSkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgYW5hbHlzaXMgcGFja2FnZXMKbGlicmFyeShicm9vbSkKbGlicmFyeShhbm5vdGFibGVzKQpgYGAKCmBgYHtyfQpzeW5Mb2dpbigpCmBgYAoKLS0tCgojIyBEYXRhIGNvbGxlY3Rpb24KCkFsbCBkYXRhIHdhcyByZXRyaWV2aWVkIGZyb20gdGhlIFsqKlBTLU9OIENlbGwgTGluZSBDaGFyYWN0ZXJpemF0aW9uIFBvcnRhbCoqXShodHRwczovL3d3dy5zeW5hcHNlLm9yZy8jIVN5bmFwc2U6c3luNzI0ODU3OCkgaW4gU3luYXBzZS4KCiMjIyBFeHByZXNzaW9uIGRhdGEKCkknbGwgZmlyc3QgZ3JhYiBiYXNpYyAic2FtcGxlIiBpbmZvIGZvciBjZWxsIGxpbmVzIGZyb20gdGhlIFsqKlByb3Rlb21pY3MgYW5kIFJOQSoqIFN5bmFwc2UgdGFibGVdKGh0dHBzOi8vd3d3LnN5bmFwc2Uub3JnLyMhU3luYXBzZTpzeW4xMDMyMDkxNC90YWJsZXMvKSwgZm9yIGFsbCBmaWxlcyBjb250YWluaW5nIFJTRU0gZ2VuZSBxdWFudGlmaWNhdGlvbiBvdXRwdXRzLgoKCmBgYHtyfQojIGNvbGxlY3QgInNhbXBsZSIgbWV0YWRhdGEgZm9yIGNlbGwgbGluZXMgd2l0aCBleHByZXNzaW9uIGRhdGEgYXZhaWxhYmxlIApwcm90X3JuYV9tZXRhX2Z2X2lkIDwtICJzeW4xMDMyMDkxNCIKCnNhbXBsZV9kZiA8LSBzeW5UYWJsZVF1ZXJ5KAogIHN0cl9nbHVlKAogICAgIlNFTEVDVCByZXBsYWNlKG5hbWUsICdfcnNlbS5nZW5lcy5yZXN1bHRzLnR4dCcsICcnKSBhcyBzYW1wbGUsIGlkLAogICAgIGRpYWdub3NpcywgY2VsbFR5cGUsIGNhdGFsb2dOdW1iZXIsIGNlbGxMaW5lLCAKICAgICBleHBlcmltZW50YWxDb25kaXRpb24sIG9yZ2FuLCB0dW1vclR5cGUgCiAgICAgRlJPTSB7ZnZfaWR9IAogICAgIFdIRVJFIHN0dWR5ID0gJ1JOQSBTdHVkeScgQU5EIG5hbWUgTElLRSAnJXJzZW0lJyIsCiAgICBmdl9pZCA9IHByb3Rfcm5hX21ldGFfZnZfaWQKICApLAogIGluY2x1ZGVSb3dJZEFuZFJvd1ZlcnNpb24gPSBGQUxTRQopICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKYGBgCgpUYWtpbmcgYSBsb29rIGF0IGBzYW1wbGVfZGZgOgoKYGBge3J9CnNhbXBsZV9kZgpgYGAKCgpJJ2xsIGRvIGEgYml0IG9mIG11bmdpbmcgd2l0aCB0aGUgYGV4cGVyaW1lbnRhbENvbmRpdGlvbmAgdmFyaWFibGUgdG8gdG8gY3JlYXRlIHNlcGFyYXRlIHZhcmlhYmxlcyBmb3IgYHN1cmZhY2VgLCBgbGFtaW5hdGVgLCBhbmQgYHN0aWZmbmVzc2AgKGluIGNhc2Ugd2Ugd2FudCB0byBicmVhayB0aGluZ3MgZG93biBmdXJ0aGVyIHdpdGggYW55IHZpc3VhbGl6YXRpb24gb3IgYW5hbHlzaXMpLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnNhbXBsZV90aWR5X2RmIDwtIHNhbXBsZV9kZiAlPiUgCiAgIyB0aWR5IHVwIGV4cGVyaW1lbnRhbCBjb25kaXRpb24gaW5mbwogIG11dGF0ZShleHBlcmltZW50YWxDb25kaXRpb24gPSBzdHJfcmVwbGFjZV9hbGwoCiAgICBleHBlcmltZW50YWxDb25kaXRpb24sICIgQWNpZCIsICJBY2lkIgogICkpICU+JQogIHNlcGFyYXRlKGV4cGVyaW1lbnRhbENvbmRpdGlvbiwKICAgICAgICAgICBpbnRvID0gYygic3RpZmZuZXNzIiwic3RpZmZuZXNzX3VuaXRzIiwgInN1cmZhY2UiKSwKICAgICAgICAgICByZW1vdmUgPSBGQUxTRSwgZXh0cmEgPSAibWVyZ2UiLCBmaWxsID0gImxlZnQiKSAlPiUKICBtdXRhdGUoCiAgICBzdXJmYWNlID0gY2FzZV93aGVuKAogICAgICBzdXJmYWNlICVpbiUgYygiQ29sbGFnZW4iLCAiRmlicm9uZWN0aW4iKSB+IGV4cGVyaW1lbnRhbENvbmRpdGlvbiwKICAgICAgVFJVRSB+IHN1cmZhY2UKICAgICksCiAgICBzdGlmZm5lc3NfdW5pdHMgPSBpZmVsc2UoaXMubmEoc3RpZmZuZXNzKSwgTkEsIHN0aWZmbmVzc191bml0cykKICApICU+JQogIHNlcGFyYXRlKHN1cmZhY2UsIGludG8gPSBjKCJzdXJmYWNlIiwgImxhbWluYXRlIikpICU+JQogIG11dGF0ZSgKICAgIHN0aWZmbmVzcyA9IHBhcnNlX251bWJlcihzdGlmZm5lc3MpLAogICAgc3RpZmZuZXNzX3VuaXRzID0gc3RyX3RyaW0oc3RpZmZuZXNzX3VuaXRzKSwKICAgIHN0aWZmbmVzc19ub3JtID0gY2FzZV93aGVuKAogICAgICBzdGlmZm5lc3NfdW5pdHMgPT0gIlBhIiB+IHN0aWZmbmVzcyAvIDEwMDAsCiAgICAgIFRSVUUgfiBzdGlmZm5lc3MKICAgICkKICApCmBgYAoKSSBhbHNvIHNwbGl0IGBzdGlmZm5lc3NfdW5pdHNgIGludG8gYSBzZXBhcmF0ZSB2YXJpYWJsZSBhbmQgYWRkZWQgYSBgc3RpZmZuZXNzX25vcm1gIHN1Y2ggdGhhdCBhbGwgdmFsdWVzIGFyZSBpbiAia1BhIiB1bml0cy4gKipOb3RlOioqIHNjcm9sbCBvdmVyIHRvIHZpZXcgdGhlIG5ldyBjb2x1bW5zLgoKYGBge3J9CnNhbXBsZV90aWR5X2RmCmBgYAoKTmV4dCwgZm9yIGFsbCB0aGUgc2FtcGxlcyBpbiBgc2FtcGxlX3RpZHlfZGZgLCBJJ2xsIGRvd25sb2FkIHRoZSBmaWxlIGNvcnJlc3BvbmRpbmcgdG8gdGhlIFN5bmFwc2UgSUQsIGV4dHJhY3QgYFRQTWAgdmFsdWVzLCBhbmQgY29tYmluZSBldmVyeXRoaW5nIGludG8gYSBzaW5nbGUgZGF0YWZyYW1lLiBFdmVuIHRob3VnaCBJJ2xsIGRvIHNvbWUgYWRkaXRpb25hbCB0cmFuc2Zvcm1hdGlvbiBhbmQgZmlsdGVyaW5nIHdpdGggdGhlIGV4cHJlc3Npb24gZGF0YSBiZWxvdywgSSdsbCBnbyBhaGVhZCBhbmQgc2F2ZSB0aGUgZnVsbCBkYXRhZnJhbWUgYXMgYCJwc29uX2V4cHJfdHBtX2RmLlJEYXRhImAuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBleHRyYWN0IGV4cHJlc3Npb24gZGF0YSBmcm9tIGluZGl2aWR1YWwgZmlsZXMKZXh0cmFjdF9leHByX3RwbSA8LSBmdW5jdGlvbihzeW5faWQpIHsKICBzeW5HZXQoc3luX2lkKSAlPiUgCiAgICBwbHVjaygicGF0aCIpICU+JSAKICAgIHJlYWRfdHN2KCkgJT4lIAogICAgc2VsZWN0KGdlbmVfaWQsIFRQTSkKfQoKc2FtcGxlX2lkX21hcCA8LSBzYW1wbGVfdGlkeV9kZiAlPiUgCiAgc2VsZWN0KHNhbXBsZSwgaWQpICU+JSAKICBkZWZyYW1lKCkKCnBzb25fZXhwcl90cG1fZmlsZSA8LSAiLi4vZGF0YS9wc29uX2V4cHJfdHBtX2RmLlJEYXRhIgppZiAoIWZzOjpmaWxlX2V4aXN0cyhwc29uX2V4cHJfdHBtX2ZpbGUpKSB7CiAgIyBjb2xsZWN0IGV4cHJlc3Npb24gZGF0YSBmb3IgYWxsIGNlbGwgbGluZXMgYW5kIGNvbmRpdGlvbnMKICBwc29uX2V4cHJfdHBtX2RmIDwtIHNhbXBsZV90aWR5X2RmICU+JSAKICAgIHBsdWNrKCJpZCIpICU+JSAKICAgIHNldF9uYW1lcygpICU+JSAKICAgIG1hcF9kZihleHRyYWN0X2V4cHJfdHBtLCAuaWQgPSAiaWQiKSAlPiUgCiAgICBzcHJlYWQoaWQsIFRQTSkgJT4lIAogICAgcmVuYW1lKCEhIXNhbXBsZV9pZF9tYXApCiAgc2F2ZShwc29uX2V4cHJfdHBtX2RmLCBmaWxlID0gcHNvbl9leHByX3RwbV9maWxlKQp9IGVsc2UgewogIGxvYWQocHNvbl9leHByX3RwbV9maWxlKQp9CmBgYAoKQ29sdW1ucyBvZiB0aGUgYHBzb25fZXhwcl90cG1fZGZgIGRhdGFmcmFtZSBjb3JyZXNwb25kIHRvIGBzYW1wbGVgIG9ic2VydmF0aW9ucyBpbiB0aGUgYHNhbXBsZV90aWR5X2RmYCBkYXRhZnJhbWU7IGVhY2ggcm93IGNvbnRhaW5zIHRoZSBUUE0gZXhwcmVzc2lvbiB2YWx1ZSBvZiB0aGUgZ2VuZSBpbmRpY2F0ZWQgYnkgdGhlIGBnZW5lX2lkYCBjb2x1bW4uIExvb2tpbmcgYXQgdGhlIGZpcnN0IGZldyByb3dzIG9ubHkgKHRoZXJlIGFyZSBgciBucm93KHBzb25fZXhwcl90cG1fZGYpYCB0b3RhbC4uLik6CgpgYGB7cn0KaGVhZChwc29uX2V4cHJfdHBtX2RmKQpgYGAKCgojIyMgTW90aWxpdHkgZGF0YQoKSSdsbCBjb2xsZWN0IG1ldGFkYXRhIGZvciBtb3RpbGl0eSBzdW1tYXJ5IGZpbGVzIGZyb20gdGhlIFsqKlBoeXNpY2FsIENoYXJhY3Rlcml6YXRpb24gVmlldyoqIFN5bmFwc2UgdGFibGVdKGh0dHBzOi8vd3d3LnN5bmFwc2Uub3JnLyMhU3luYXBzZTpzeW43NzQ3NzM0L3RhYmxlcy8pIChhZ2FpbiwgZm9yIGFsbCBjZWxsIGxpbmVzKS4gSSBvbmx5IG5lZWQgdGhlIGBpZGAsIGBjYXRhbG9nTnVtYmVyYCwgYW5kIGBleHBlcmltZW50YWxDb25kaXRpb25gIGNvbHVtbnMgc28gdGhhdCBJIChpKSBjYW4gZG93bmxvYWQgZWFjaCBmaWxlIGJlbG93IGFuZCAoaWkpIGNvcnJlY3RseSBtYXRjaCB1cCBzYW1wbGUgaW5mb3JtYXRpb24gZnJvbSB0aGUgZXhwcmVzc2lvbiBkYXRhLiAKCmBgYHtyfQojIHJldHJpZXZlIGNlbGwgbGluZSBtb3RpbGl0eSBtZXRhZGF0YSBmcm9tIFN5bmFwc2UgZmlsZSB2aWV3Cm1vdGlsX21ldGFfZnZfaWQgPC0gInN5bjc3NDc3MzQiCgpwc29uX21vdGlsX21ldGFfZGYgPC0gc3luVGFibGVRdWVyeSgKICBzdHJfZ2x1ZSgiU0VMRUNUIGlkLCBjYXRhbG9nTnVtYmVyLCBleHBlcmltZW50YWxDb25kaXRpb24gRlJPTSB7ZnZfaWR9IAogICAgICAgICAgICBXSEVSRSBzdHVkeSA9ICdNb3RpbGl0eScgQU5EIG5hbWUgTElLRSAnJXN1bW1hcnklJyIsIAogICAgICAgICAgIGZ2X2lkID0gbW90aWxfbWV0YV9mdl9pZCksCiAgaW5jbHVkZVJvd0lkQW5kUm93VmVyc2lvbiA9IEZBTFNFCikgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgIyBmb3JtYXQgZXhwZXJpbWVudGFsIGNvbmRpdGlvbiB0byBtYXRjaCBzYW1wbGUgZGF0YWZyYW1lIGFib3ZlCiAgbXV0YXRlKGV4cGVyaW1lbnRhbENvbmRpdGlvbiA9IHN0cl9yZXBsYWNlX2FsbCgKICAgIGV4cGVyaW1lbnRhbENvbmRpdGlvbiwgIiBBY2lkIiwgIkFjaWQiCiAgKSkKYGBgCgpIZXJlJ3Mgd2hhdCB0aGUgbWV0YWRhdGEgZGF0YWZyYW1lIGxvb2tzIGxpa2UgKCoqbm90ZToqKiBvbmx5IDkgb2YgdGhlIGByIG5fZGlzdGluY3QocHNvbl9tb3RpbF9tZXRhX2RmJGNhdGFsb2dOdW1iZXIpYCBjZWxsIGxpbmVzIGhhdmUgY29ycmVzcG9uZGluZyBleHByZXNzaW9uIGRhdGEgZm9yIHRoZSBtb3RpbGl0eSBjb25kaXRpb25zIHRlc3RlZCkuCgpgYGB7cn0KcHNvbl9tb3RpbF9tZXRhX2RmCmBgYAoKXCAgCgo+ICoqQXNpZGU6KiogZm9ybWF0IGFuZCBzYXZlIHNhbXBsZSBkYXRhCgpJJ2xsIGdvIGFoZWFkIGFuZCBhZGQgU3luYXBzZSBJRHMgZm9yIHRoZSBtb3RpbGl0eSBzdW1tYXJ5IGZpbGVzIHRvIGBzYW1wbGVfdGlkeV9kZmAuIFRoZSByZXN1bHRpbmcgYHNhbXBsZV9tYXBfZGZgIGRhdGFmcmFtZSBjb250YWlucyBhbGwgc2FtcGxlIGluZm9ybWF0aW9uIGFuZCBmaWxlIElEcyBmb3IgYm90aCBleHByZXNzaW9uIGFuZCBtb3RpbGl0eSBmaWxlcyAoYGlkX2V4cHJgIGFuZCBgaWRfbW90aWxgLCByZXNwZWN0aXZlbHkpLiAKSSdsbCBhbHNvIGNyZWF0ZSBhIGNvbG9yIHBhbGV0dGUgdGhhdCBJIGNhbiB1c2UgZm9yIGluZGljYXRpbmcgZGlhZ25vc2lzIChjYW5jZXIgdHlwZSkgaW4gcGxvdHMgYmVsb3cuIEknbGwgc2F2ZSBib3RoIHRoZSBgc2FtcGxlX21hcF9kZmAgZGF0YWZyYW1lIGFuZCBgZGlhZ25vc2lzX2NvbG9yc2AgbGlzdCBhcyBgInBzb25fc2FtcGxlX21hcF9hbmRfY29sb3JzLlJEYXRhImAuCgpgYGB7cn0KIyBidWlsZCBhbmQgc2F2ZSBtYXN0ZXIgc2FtcGxlIGRhdGFmcmFtZQpzYW1wbGVfbWFwX2RmIDwtIHNhbXBsZV90aWR5X2RmICU+JSAKICBsZWZ0X2pvaW4ocHNvbl9tb3RpbF9tZXRhX2RmLCAKICAgICAgICAgICAgYnkgPSBjKCJjYXRhbG9nTnVtYmVyIiwgImV4cGVyaW1lbnRhbENvbmRpdGlvbiIpLAogICAgICAgICAgICBzdWZmaXggPSBjKCJfZXhwciIsICJfbW90aWwiKSkKCmRpYWdub3Npc19jb2xvcnMgPC0gc2FtcGxlX21hcF9kZiAlPiUgCiAgZGlzdGluY3QoZGlhZ25vc2lzKSAlPiUgCiAgbXV0YXRlKGNvbG9yID0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKCJjb2xvcmJsaW5kMTAiKShucm93KC4pKSkgJT4lIAogIGRlZnJhbWUoKQoKc2F2ZShzYW1wbGVfbWFwX2RmLCBmaWxlID0gIi4uL2RhdGEvcHNvbl9zYW1wbGVfbWFwX2FuZF9jb2xvcnMuUkRhdGEiKQpgYGAKCkhlcmUncyB3aGF0IHRoZSBmaW5hbCBzYW1wbGUgaW5mbyBkYXRhZnJhbWUgbG9va3MgbGlrZToKCmBgYHtyfQpzYW1wbGVfbWFwX2RmCmBgYAoKQW5kIGhlcmUgYXJlIG91ciBkaWFnbm9zaXMgY29sb3JzOgoKYGBge3J9CmRpYWdub3Npc19jb2xvcnMKYGBgCgoKU2ltaWxhciB0byB0aGUgZXhwcmVzc2lvbiBkYXRhIGFib3ZlLCBJJ2xsIGRvd25sb2FkIHRoZSBtb3RpbGl0eSBzdW1tYXJ5IGZpbGVzIGZvciBhbGwgY2VsbCBsaW5lcyBhbmQgYWxsIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zLCByZWFkIGluIHRoZSBkYXRhLCBmb3JtYXQgdGhpbmdzIGEgYml0LCBhbmQgY29tYmluZSBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZS4gSSdsbCBzYXZlIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lIGFzIGAicHNvbl9tb3RpbF9zdW1tYXJ5X2RmLlJEYXRhImAuICoqTm90ZToqKiBJJ2xsIGFwcGVuZCBjb2x1bW5zIGZyb20gYHNhbXBsZV9tYXBfZGZgIHRvIHRoZSBtb3RpbGl0eSBzdW1tYXJ5IGRhdGFmcmFtZSB0byBhdm9pZCBhZGRpdGlvbmFsIGBqb2luYCBzdGVwcyBiZWxvdyAodGhlIGRhdGFmcmFtZSBpcyBzdGlsbCByZWxhdGl2ZWx5IHNtYWxsKS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQojIGV4dHJhY3QgbW90aWxpdHkgc3VtbWFyeSBkYXRhIGZyb20gaW5kaXZpZHVhbCBmaWxlcwpleHRyYWN0X21vdGlsX3N1bW1hcnkgPC0gZnVuY3Rpb24oc3luX2lkKSB7CiAgaWYgKCFpcy5uYShzeW5faWQpKSB7CiAgICBzeW5HZXQoc3luX2lkKSAlPiUgCiAgICAgIHBsdWNrKCJwYXRoIikgJT4lIAogICAgICAjIG9ubHkgdGFrZSB0aGUgZmlyc3QgNCByb3dzOyBhbnl0aGluZyBhZnRlciB0aGF0IGlzIHVuc3RydWN0dXJlZCBhbmQgbWlnaHQKICAgICAgIyBjYXVzZSBlcnJvcnMKICAgICAgcmVhZF90c3Yobl9tYXggPSA0KSAlPiUgCiAgICAgICMgY2xlYW4gYW5kIHN0YW5kYXJkaXplIGNvbHVtbnMgYW5kIHJvdyBuYW1lcyBiYXNlZCBvbiByZWFkbWUgaW5mbyAKICAgICAgIyBmdHA6Ly9jYWZ0cGQubmNpLm5paC5nb3YvcHNvbmRjYy9QaHlzaWNhbENoYXJhY3Rlcml6YXRpb24vTW90aWxpdHkvUkVBRE1FLnR4dAogICAgICBjbGVhbl9uYW1lcygpICU+JSAKICAgICAgbXV0YXRlKHN0YXRpc3RpYyA9IGMoCiAgICAgICAgImF2ZXJhZ2VfdmFsdWUiLCAKICAgICAgICAidG90YWxfbnVtYmVyX29mX2NlbGxzX3RyYWNrZWQiICwKICAgICAgICAic3RhbmRhcmRfZGV2aWF0aW9uIiwgCiAgICAgICAgInN0YW5kYXJkX2Vycm9yIgogICAgICApKSAlPiUgCiAgICAgIGdhdGhlcihzdW1tYXJ5X21ldHJpYywgdmFsdWUsIC1zdGF0aXN0aWMpICU+JSAKICAgICAgc3ByZWFkKHN0YXRpc3RpYywgdmFsdWUpCiAgfSBlbHNlIHsKICAgIGRhdGEuZnJhbWUoKQogIH0KfQoKcHNvbl9tb3RpbF9zdW1tYXJ5X2ZpbGUgPC0gIi4uL2RhdGEvcHNvbl9tb3RpbGl0eV9zdW1tYXJ5X2RmLlJEYXRhIgppZiAoIWZzOjpmaWxlX2V4aXN0cyhwc29uX21vdGlsX3N1bW1hcnlfZmlsZSkpIHsKICAjIGNvbGxlY3Qgc3VtbWFyeSBkYXRhIGZvciBhbGwgY2VsbCBsaW5lcyBhbmQgY29uZGl0aW9ucwogIHBzb25fbW90aWxfc3VtbWFyeV9kZiA8LSBzYW1wbGVfbWFwX2RmICU+JSAKICAgIHBsdWNrKCJpZF9tb3RpbCIpICU+JSAKICAgIHNldF9uYW1lcygpICU+JSAKICAgIG1hcF9kZihleHRyYWN0X21vdGlsX3N1bW1hcnksIC5pZCA9ICJpZF9tb3RpbCIpICU+JSAKICAgIGxlZnRfam9pbigKICAgICAgc2FtcGxlX21hcF9kZiwKICAgICAgYnkgPSAiaWRfbW90aWwiCiAgICApCiAgc2F2ZShwc29uX21vdGlsX3N1bW1hcnlfZGYsIGZpbGUgPSBwc29uX21vdGlsX3N1bW1hcnlfZmlsZSkKfSBlbHNlIHsKICBsb2FkKHBzb25fbW90aWxfc3VtbWFyeV9maWxlKQp9CmBgYAoKLS0tCgojIyBEYXRhIGZvcm1hdHRpbmcKCk5vdyB0aGF0IEkgaGF2ZSB0aGUgYmFzaWMgaW5mb3JtYXRpb24gZG93bmxvYWRlZCBhbmQgc3RvcmVkIGluIHJlbGF0aXZlbHkgdXNhYmxlIGZvcm1hdHMsIEknbGwgZG8gYSBiaXQgb2YgdGlkeWluZyBhbmQgbWluaW1hbCAiZmVhdHVyZSBlbmdpbmVlcmluZyIgdG8gZmFjaWxpYXRlIHZpc3VhbGl6YXRpb24gYW5kIGFuYWx5c2lzIGJlbG93LgoKIyMjIEV4cHJlc3Npb24gZGF0YQoKRm9yIHRoZSBleHByZXNzaW9uIGRhdGEsIEknbGwganVzdCBjb252ZXJ0IHRoZSBkYXRhZnJhbWUgb2YgVFBNIHZhbHVlcyBpbnRvIGEgbWF0cml4IGFuZCBzYXZlIHRoZSBmaWxlIHRvIHNoYXJlIChwcm92aWRpbmcgYW4gYWx0ZXJuYXRpdmUgZm9ybWF0IGZvciBhbnlvbmUgd2hvIHdhbnRzIHRvIHVzZSBpdCkuIEkgY291bGQgYWxzbyBub3JtYWxpemUgdGhlIGRhdGEgKGkuZS4sIGxvZyB0cmFuc2Zvcm0pLCBidXQgSSdsbCBzYXZlIHRoYXQgZm9yIHRoZSBhbmFseXNpcyBzdGVwcy4KCiMjIyMgU2F2ZSBleHByZXNzaW9uIG1hdHJpeCBmaWxlCgpgYGB7cn0KcHNvbl9leHByX3RwbV9tYXRfZmlsZSA8LSAiLi4vZGF0YS9wc29uX2V4cHJfdHBtX21hdC5SRGF0YSIKaWYgKCFmczo6ZmlsZV9leGlzdHMocHNvbl9leHByX3RwbV9tYXRfZmlsZSkpIHsKICBwc29uX2V4cHJfdHBtX21hdCA8LSBwc29uX2V4cHJfdHBtX2RmICU+JSAKICAgIGNvbHVtbl90b19yb3duYW1lcygiZ2VuZV9pZCIpICU+JSAKICAgIGFzLm1hdHJpeCgpCiAgc2F2ZShwc29uX2V4cHJfdHBtX21hdCwgZmlsZSA9IHBzb25fZXhwcl90cG1fbWF0X2ZpbGUpCn0gZWxzZSB7CiAgbG9hZChwc29uX2V4cHJfdHBtX21hdF9maWxlKQp9CmBgYAoKIyMjIyBDcmVhdGUgYW5kIHNhdmUgZ2VuZSBpbmZvIGRhdGFmcmFtZQoKSSdsbCB1c2UgdGhlIGBhbm5vdGFibGVzYCBwYWNrYWdlIHRvIGF1Z21lbnQgdGhlIEVuc2VtYmwgZ2VuZSBJRHMgd2l0aCBzb21lIGFkZGl0aW9uYWwgZmllbGQgdG8gcHJvdmlkZSBtb3JlIGNvbnRleHQ6IAoKKyBgZW50cmV6YDogTkNCSSBFbnRyZXogZ2VuZSBJRAorIGBzeW1ib2xgOiBIR05DIGdlbmUgc3ltYm9sCisgYGJpb3R5cGVgOiBQcm90ZWluIGNvZGluZywgcHNldWRvZ2VuZSwgbWl0b2Nob25kcmlhbCB0Uk5BLCBldGMuCisgYGRlc2NyaXB0aW9uYDogRnVsbCBnZW5lIG5hbWUvZGVzY3JpcHRpb24KCkknbGwgc2F2ZSB0aGlzIGRhdGFmcmFtZSBhcyBgInBzb25fZXhwcl9nZW5lX2RmLlJEYXRhImAuCgpgYGB7cn0KZ2VuZV9kZiA8LSBwc29uX2V4cHJfdHBtX2RmICU+JSAKICBzZWxlY3QoZ2VuZV9pZCkgJT4lIAogIGxlZnRfam9pbihncmNoMzgsIGJ5ID0gYygiZ2VuZV9pZCIgPSAiZW5zZ2VuZSIpKSAlPiUgCiAgc2VsZWN0KGdlbmVfaWQsIGVudHJleiwgc3ltYm9sLCBiaW90eXBlLCBkZXNjcmlwdGlvbikKc2F2ZShnZW5lX2RmLCBmaWxlID0gIi4uL2RhdGEvcHNvbl9leHByX2dlbmVfaW5mby5SRGF0YSIpCmBgYAoKVGFraW5nIGEgbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgYGdlbmVfZGZgOgoKYGBge3J9CmhlYWQoZ2VuZV9kZikKYGBgCgojIyMgTW90aWxpdHkgZGF0YSAKCkkgaGFwcGVuIHRvIGtub3cgZnJvbSBzb21lIGVhcmxpZXIgaW5zcGVjdGlvbiBvZiB0aGUgbW90aWxpdHkgZGF0YSB0aGF0IHNjYWxlcyBmb3IgYXZlcmFnZSB2YWx1ZXMgYXJlIGZhaXJseSBkaWZmZXJlbnQgYWNyb3NzIGNlbGwgbGluZXMuIEknbGwga2VlcCB0aGUgb3JpZ2luYWwgdmFsdWVzIGluIHRoZSBkYXRhZnJhbWUsIGJ1dCBhZGQgKmNlbnRlcmVkIGFuZCBzY2FsZWQqIHZlcnNpb25zICh1c2luZyB0aGUgYHNjYWxlKClgIGZ1bmN0aW9uKS4gSSdsbCBzYXZlIHRoZSBhdWdtZW50ZWQgZGF0YWZyYW1lIGFzIGAicHNvbl9tb3RpbGl0eV90aWR5X2RmLlJEYXRhImAgYW5kIHVzZSB0aGlzIGZvciBhbmFseXNpcyBiZWxvdy4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpzY2FsZV90aGlzIDwtIGZ1bmN0aW9uKHgpIGFzLnZlY3RvcihzY2FsZSh4KSkKCnBzb25fbW90aWxfdGlkeV9maWxlIDwtICIuLi9kYXRhL3Bzb25fbW90aWxpdHlfdGlkeV9kZi5SRGF0YSIKaWYgKCFmczo6ZmlsZV9leGlzdHMocHNvbl9tb3RpbF90aWR5X2ZpbGUpKSB7CiAgIyBzY2FsZSBhbmQgY2VudGVyIG1vdGlsaXR5IG1lYXN1cmVtZW50IGRhdGEKICBwc29uX21vdGlsX3RpZHlfZGYgPC0gcHNvbl9tb3RpbF9zdW1tYXJ5X2RmICU+JQogICAgZ3JvdXBfYnkoY2VsbExpbmUsIHN1bW1hcnlfbWV0cmljKSAlPiUgCiAgICBtdXRhdGUoYXZlcmFnZV92YWx1ZV9zY2FsZWQgPSBzY2FsZV90aGlzKGF2ZXJhZ2VfdmFsdWUpKSAlPiUKICAgIHVuZ3JvdXAoKQogIHNhdmUocHNvbl9tb3RpbF90aWR5X2RmLCBmaWxlID0gcHNvbl9tb3RpbF90aWR5X2ZpbGUpCn0gZWxzZSB7CiAgbG9hZChwc29uX21vdGlsX3RpZHlfZmlsZSkKfQpgYGAKCi0tLQoKIyMgRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcwoKQ2hlY2tpbmcgb3V0IHRyZW5kcyBhbmQgZGlzdHJpYnV0aW9ucyBmb3IgbW90aWxpdHkgYW5kIGV4cHJlc3Npb24gZGF0YS4KCiMjIyBDZWxsIGxpbmUgbW90aWxpdHkgb3ZlcnZpZXcKCkknbGwgZG8gYSBiaXQgb2YgZm9ybWF0dGluZyBhbmQgYXJyYW5naW5nIHRvIGNvbnRyb2wgdGhlIG9yZGVyIGluIHdoaWNoIGNlbGwgbGluZXMgYW5kIGNhbmNlciB0eXBlcyBhcmUgcGxvdHRlZCAodG8gaG9wZWZ1bGx5IG1ha2UgdHJlbmRzIGVhc2llciB0byBzZWUpLgoKYGBge3J9CiMgZm9ybWF0IG1vdGlsaXR5IGRhdGEgZm9yIHBsb3R0aW5nIChzZXQgdXAgZmFjdG9ycyBmb3IgYXhpcyBvcmRlcmluZywgZXRjLikKcGxvdF9kZiA8LSBwc29uX21vdGlsX3RpZHlfZGYgJT4lCiAgZ3JvdXBfYnkoZGlhZ25vc2lzLCBjZWxsTGluZSkgJT4lIAogIG11dGF0ZSgKICAgIGN2X3NjYWxlZF92YWx1ZSA9IHNkKGF2ZXJhZ2VfdmFsdWVfc2NhbGVkKSAvIG1lYW4oYXZlcmFnZV92YWx1ZV9zY2FsZWQpCiAgKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBhcnJhbmdlKGRlc2MoY3Zfc2NhbGVkX3ZhbHVlKSkgJT4lIAogIHJlcGxhY2VfbmEobGlzdChzdGlmZm5lc3Nfbm9ybSA9ICJOQSIsIGxhbWluYXRlID0gIk5BIikpICU+JSAKICBtdXRhdGUoZGlhZ25vc2lzID0gZmN0X2lub3JkZXIoZGlhZ25vc2lzKSwKICAgICAgICAgZGlhZ25vc2lzID0gZmN0X3JlbGV2ZWwoZGlhZ25vc2lzLCAiTm90IEFwcGxpY2FibGUiLCBhZnRlciA9IEluZikpICU+JSAKICBhcnJhbmdlKGFzLmludGVnZXIoZGlhZ25vc2lzKSkgJT4lIAogIG11dGF0ZShjZWxsTGluZSA9IGZjdF9pbm9yZGVyKGNlbGxMaW5lKSkKYGBgCgpBcyBhIHF1aWNrIHNhbml0eSBjaGVjaywgSSdsbCB2ZXJpZnkgdGhhdCBJIGNhbiByZXByb2R1Y2UgdGhlIHBsb3QgSSBwcmV2aW91c2x5IG1hZGUgaW4gdGhlIGBtb3RpbGl0eV9icmNhLlJgIHNjcmlwdDoKCmBgYHtyfQpwbG90X2RmICU+JSAKICBmaWx0ZXIoZGlhZ25vc2lzICVpbiUgYygiQnJlYXN0IENhbmNlciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjZWxsTGluZSwgeSA9IGF2ZXJhZ2VfdmFsdWUpKSArCiAgZ2VvbV9jb2woYWVzKGZpbGwgPSBsYW1pbmF0ZSwgYWxwaGEgPSBzdGlmZm5lc3Nfbm9ybSksIAogICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC45KSkgKwogIGdlb21fZXJyb3JiYXIoCiAgICBhZXMoeW1pbiA9IGF2ZXJhZ2VfdmFsdWUgLSBzdGFuZGFyZF9lcnJvciwgCiAgICAgICAgeW1heCA9IGF2ZXJhZ2VfdmFsdWUgKyBzdGFuZGFyZF9lcnJvciwgCiAgICAgICAgY29sb3IgPSBsYW1pbmF0ZSwgCiAgICAgICAgYWxwaGEgPSBzdGlmZm5lc3Nfbm9ybSksIAogICAgc2l6ZSA9IDAuNSwgd2lkdGggPSAwLjI1LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOSkKICApICsKICBzY2FsZV9hbHBoYV9tYW51YWwoInN0aWZmbmVzcyBba1BhXSIsIHZhbHVlcyA9IGMoMC4zLCAxLCAxKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICJibGFjayIsICJibGFjayIpKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGZhY2V0X2dyaWQoc3VtbWFyeV9tZXRyaWMgfiBzdXJmYWNlLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGxhYnModGl0bGUgPSAiQXZlcmFnZSBtb3RpbGl0eSBtZWFzdXJlcyB2cy4gc3VyZmFjZSIpICsKICB4bGFiKCJjZWxsIGxpbmUiKSArCiAgeWxhYigiIikgKwogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCiMjIyMgQXZlcmFnZSB2YWx1ZXMKCldoaWxlIEkgZG9uJ3QgaGF2ZSBhIHN0cm9uZyBqdXN0aWZpY2F0aW9uLCBJJ3ZlIGRlY2lkZWQgdXAgZnJvbnQgdGhhdCBJJ20gbm90IGludGVyZXN0ZWQgaW4gdGhlICJlbmQtdG8tZW5kIGRpc3RhbmNlIiAoYGVuZF90b19lbmRfZGlzdGFuY2VfdW1gKSBzdW1tYXJ5IG1lYXN1cmUgZm9yIG1vdGlsaXR5LiBXaXRoIHRoYXQsIEkgc3RpbGwgaGF2ZSAqKjIqKiBzdW1tYXJ5IG1ldHJpY3MgZm9yICoqOSoqIGNlbGwgbGluZXMsIGNhdGVnb3JpemVkIGludG8gKio2KiogZGlhZ25vc2VzIC8gY2FuY2VyIHR5cGVzLCBhY3Jvc3MgKio3KiogY29uZGl0aW9ucy4gVGhhdCdzIGEgbG90IG9mIGluZm9ybWF0aW9uIHRvIGNhcHR1cmUgaW4gb25lIHBsb3QgYnV0IEknbGwgdHJ5IHRvIHdvcmsgd2l0aCBoZWF0bWFwcy4gVGhlIGBwYXRjaHdvcmtgIHBhY2thZ2UgY29tZXMgaW4gaGFuZHkgaGVyZSBmb3IgY29tYmluaW5nIHBhbmVscyB3aXRoIGRpZmZlcmVudCBjb2xvciBwYWxldHRlcy4KCmBgYHtyfQpwX3NwZWVkIDwtIHBsb3RfZGYgJT4lIAogIGZpbHRlcihzdW1tYXJ5X21ldHJpYyA9PSAic3BlZWRfdW1faHIiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gY2VsbExpbmUsIHkgPSBleHBlcmltZW50YWxDb25kaXRpb24pKSArIAogIGdlb21fdGlsZShhZXMoZmlsbCA9IGF2ZXJhZ2VfdmFsdWUpLCBjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC4yKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCJBdmVyYWdlIHNwZWVkIFt1bS9ocl0iKSArIAogIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3JiYXIoZGlyZWN0aW9uID0gImhvcml6b250YWwiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlLnBvc2l0aW9uID0gInRvcCIpKSArCiAgeGxhYigiIikgKyAKICB5bGFiKCIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBmYWNldF9ncmlkKHN1cmZhY2UgfiBzdW1tYXJ5X21ldHJpYywgc2NhbGVzID0gImZyZWVfeSIsIHNwYWNlID0gImZyZWVfeSIpCgpwX2Rpc3QgPC0gcGxvdF9kZiAlPiUgCiAgZmlsdGVyKHN1bW1hcnlfbWV0cmljID09ICJ0b3RhbF9kaXN0YW5jZV91bSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjZWxsTGluZSwgeSA9IGV4cGVyaW1lbnRhbENvbmRpdGlvbikpICsgCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gYXZlcmFnZV92YWx1ZSksIGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjIpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoIkF2ZXJhZ2UgZGlzdGFuY2UgW3VtXSIsIG9wdGlvbiA9IDMpICsgCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9jb2xvcmJhcihkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUucG9zaXRpb24gPSAidG9wIikpICsKICB4bGFiKCIiKSArIAogIHlsYWIoIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gLTEwKSkgKwogIGZhY2V0X2dyaWQoc3VyZmFjZSB+IHN1bW1hcnlfbWV0cmljLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3BhY2UgPSAiZnJlZV95IikKCnBfZGlhZyA8LSBwbG90X2RmICU+JSAKICBmaWx0ZXIoc3VtbWFyeV9tZXRyaWMgPT0gInNwZWVkX3VtX2hyIikgJT4lIAogIGdncGxvdChhZXMoeCA9IGNlbGxMaW5lLCB5ID0gMSkpICsgCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gZGlhZ25vc2lzKSwgY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDAuMikgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBkaWFnbm9zaXNfY29sb3JzKSArCiAgeWxhYigiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQubWFyZ2luID0gbWFyZ2luKHQgPSA4MCksCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgbGVnZW5kLmtleS5zaXplID0gZ2dwbG90Mjo6dW5pdCg4LCAicHQiKSwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAtNSwgYiA9IDEwKSkgKwogIGZhY2V0X2dyaWQoLiB+IHN1bW1hcnlfbWV0cmljLCBzY2FsZXMgPSAiZnJlZV95Iiwgc3BhY2UgPSAiZnJlZV95IikKCnBfY29tYiA8LSBwX3NwZWVkICsgcF9kaXN0ICsgcF9kaWFnICsgCiAgcGxvdF9sYXlvdXQobmNvbCA9IDEsIGhlaWdodHMgPSBjKDIwLCAyMCwgMi41KSkKcF9jb21iCmBgYAoKIyMjIyBTY2FsZWQgYW5kIGNlbnRlcmVkCgpBcyBleHBlY3RlZCwgdGhlIGRpZmZlcmluZyBzY2FsZXMgb2YgbW90aWxpdHkgbWV0cmljcyBhY3Jvc3MgY29uZGl0aW9ucyBmb3IgZWFjaCBjZWxsIGxpbmUgbWFrZSBpdCB0b3VnaCB0byBkaXNjZXJuIGFueSBwYXR0ZXJucy4gSSdsbCB0cnkgYWdhaW4gd2l0aCB0aGUgY2VudGVyZWQgYW5kIHNjYWxlZCBhdmVyYWdlIHZhbHVlcy4gKipOb3RlOioqIHRoaXMgaXMgYWRtaXR0ZWRseSBhIGxvdCBvZiBkdXBsaWNhdGVkIGNvZGUgdG8gcHJvZHVjZSBhIHBsb3Qgd2l0aCBvbmx5IG1pbm9yIGNoYW5nZXMgaW4gY29udGVudC4KCmBgYHtyfQpwX2RpYWcgPC0gcGxvdF9kZiAlPiUgCiAgZmlsdGVyKHN1bW1hcnlfbWV0cmljID09ICJzcGVlZF91bV9ociIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjZWxsTGluZSwgeSA9IDEpKSArIAogIGdlb21fdGlsZShhZXMoZmlsbCA9IGRpYWdub3NpcyksIGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjIpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZGlhZ25vc2lzX2NvbG9ycykgKwogIHNjYWxlX3hfZGlzY3JldGUocG9zaXRpb24gPSAidG9wIikgKyAKICB4bGFiKCIiKSArIAogIHlsYWIoIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMC41KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLm1hcmdpbiA9IG1hcmdpbih0ID0gLTYwKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBsZWdlbmQua2V5LnNpemUgPSBnZ3Bsb3QyOjp1bml0KDgsICJwdCIpLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4odCA9IC01LCBiID0gMTApKSArCiAgZmFjZXRfZ3JpZCguIH4gc3VtbWFyeV9tZXRyaWMsIHNjYWxlcyA9ICJmcmVlX3kiLCBzcGFjZSA9ICJmcmVlX3kiKQoKcF9zcGVlZCA8LSBwbG90X2RmICU+JSAKICBmaWx0ZXIoc3VtbWFyeV9tZXRyaWMgPT0gInNwZWVkX3VtX2hyIikgJT4lIAogIGdncGxvdChhZXMoeCA9IGNlbGxMaW5lLCB5ID0gZXhwZXJpbWVudGFsQ29uZGl0aW9uKSkgKyAKICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBhdmVyYWdlX3ZhbHVlX3NjYWxlZCksIGNvbG91ciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjIpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoIkF2ZXJhZ2Ugc3BlZWQqIFt1bS9ocl0iKSArIAogIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3JiYXIoZGlyZWN0aW9uID0gImhvcml6b250YWwiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlLnBvc2l0aW9uID0gInRvcCIpKSArCiAgeGxhYigiIikgKyAKICB5bGFiKCIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBmYWNldF9ncmlkKHN1cmZhY2UgfiBzdW1tYXJ5X21ldHJpYywgc2NhbGVzID0gImZyZWVfeSIsIHNwYWNlID0gImZyZWVfeSIpCgpwX2Rpc3QgPC0gcGxvdF9kZiAlPiUgCiAgZmlsdGVyKHN1bW1hcnlfbWV0cmljID09ICJ0b3RhbF9kaXN0YW5jZV91bSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjZWxsTGluZSwgeSA9IGV4cGVyaW1lbnRhbENvbmRpdGlvbikpICsgCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gYXZlcmFnZV92YWx1ZV9zY2FsZWQpLCBjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC4yKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCJBdmVyYWdlIGRpc3RhbmNlKiBbdW1dIiwgb3B0aW9uID0gMykgKyAKICBndWlkZXMoZmlsbCA9IGd1aWRlX2NvbG9yYmFyKGRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZS5wb3NpdGlvbiA9ICJ0b3AiKSkgKwogIHhsYWIoIiIpICsgCiAgeWxhYigiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKGIgPSAxMCkpICsKICBmYWNldF9ncmlkKHN1cmZhY2UgfiBzdW1tYXJ5X21ldHJpYywgc2NhbGVzID0gImZyZWVfeSIsIHNwYWNlID0gImZyZWVfeSIpCgoKCnBfY29tYiA8LSBwX2RpYWcgKyBwX3NwZWVkICsgcF9kaXN0ICsgCiAgcGxvdF9sYXlvdXQobmNvbCA9IDEsIGhlaWdodHMgPSBjKDIuNSwgMjAsIDIwKSkgKwogIGxhYnMoY2FwdGlvbiA9ICIqIGNlbnRlcmVkIGFuZCBzY2FsZWQiKQpwX2NvbWIKYGBgCgpTdGlsbCBub3RoaW5nIGFzIG9idmlvdXMgb3IgcHJvbm91bmNlZCBhcyBJIG1pZ2h0IGhhdmUgaG9wZWQuIEZvciBleGFtcGxlLCBub25lIG9mIHRoZSBjb25kaXRpb25zIHNlZW0gdG8gYmUgdW5pdmVyc2FsbHkgYXNzb2NpYXRlZCB3aXRoIGluY3JlYXNlZCAob3IgZGVjcmVhc2VkKSBtb3RpbGl0eSByZWxhdGl2ZSB0byB0aGUgb3RoZXJzIOKAlCBldmVuICJnbGFzcyIgaXMgdGhlIGxvd2VzdCB2YWx1ZSBmb3Igc29tZSBjZWxsIGxpbmVzIGFuZCB0aGUgaGlnaGVzdCBmb3Igb3RoZXJzLiBJIGd1ZXNzIHRoZSB0YWtlYXdheSBpcyB0aGF0IG1vdGlsaXR5IHJlZ3VsYXRpb24gYW5kIHJlc3BvbnNlIGlzIGNlbGwtdHlwZSBzcGVjaWZpYyDigJQgdGhvdWdoIHRoZXJlIG1pZ2h0IGJlIGNvbW1vbiBkcml2ZXJzIG9uIHRoZSBtb2xlY3VsYXIgbGV2ZWwuCgojIyMjIE1vdGlsaXR5IHZzLiBjb25kaXRpb24KCkhlcmUncyBhIGRpZmZlcmVudCB3YXkgdG8gbG9vayBhdCBwb3NzaWJsZSB0cmVuZHMgb2YgY2VsbCBsaW5lIG1vdGlsaXR5IHdpdGggcmVzcGVjdCB0byBleHBlcmltZW50YWwgY29uZGl0aW9uOgoKYGBge3J9CnBzb25fbW90aWxfdGlkeV9kZiAlPiUgCiAgZmlsdGVyKHN1bW1hcnlfbWV0cmljICE9ICJlbmRfdG9fZW5kX2Rpc3RhbmNlX3VtIikgJT4lIAogIG11dGF0ZShleHBlcmltZW50YWxDb25kaXRpb24gPSBmY3RfcmV2KGV4cGVyaW1lbnRhbENvbmRpdGlvbiksCiAgICAgICAgIGV4cGVyaW1lbnRhbENvbmRpdGlvbiA9IGZjdF9yZWxldmVsKGV4cGVyaW1lbnRhbENvbmRpdGlvbiwgIkdsYXNzIikpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBleHBlcmltZW50YWxDb25kaXRpb24sIHkgPSBhdmVyYWdlX3ZhbHVlKSkgKyAKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBkaWFnbm9zaXMpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvdXIgPSBkaWFnbm9zaXMsIGdyb3VwID0gY2VsbExpbmUpKSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbCgKICAgIGRhdGEgPSBwc29uX21vdGlsX3RpZHlfZGYgJT4lICAKICAgICAgZmlsdGVyKGV4cGVyaW1lbnRhbENvbmRpdGlvbiA9PSAiMzAga1BhIHBvbHlhY3J5bGFtaWRlIENvbGxhZ2VuIiwKICAgICAgICAgICAgIHN1bW1hcnlfbWV0cmljICE9ICJlbmRfdG9fZW5kX2Rpc3RhbmNlX3VtIiksCiAgICBhZXMobGFiZWwgPSBjZWxsTGluZSksCiAgICBzaXplID0gMywgbnVkZ2VfeSA9IDUsIGFscGhhID0gMC43LCBsYWJlbC5wYWRkaW5nID0gdW5pdCgwLjEsICJsaW5lcyIpCiAgKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBkaWFnbm9zaXNfY29sb3JzKSArIAogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGZhY2V0X2dyaWQoc3VtbWFyeV9tZXRyaWMgfiBkaWFnbm9zaXMsIHNjYWxlcyA9ICJmcmVlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgc2l6ZSA9IDgpKQo/Z2VvbV9sYWJlbF9yZXBlbApgYGAKCiMjIyMgRGlzdHJpYnV0aW9ucwoKVGhpcyBpcyBhIHF1aWNrIGNoZWNrIHRvIHNlZSBob3cgbW90aWxpdHkgbWV0cmljIHZhbHVlcyBhcmUgZGlzdHJpYnV0ZWQgYWNyb3NzIGNvbmRpdGlvbnMgZm9yIGVhY2ggY2VsbCBsaW5lLiBJJ20gbG9va2luZyBmb3Igc29tZSBzZW1ibGFuY2Ugb2Ygbm9ybWFsaXR5LCB0aG91Z2ggdGhlIHNhbXBsZSBzaXplIGlzIGZhaXJseSBzbWFsbC4gSSdsbCBsaWtlbHkgY2hlY2sgYXNzb2NpYXRpb25zIHdpdGggYm90aCBwYXJhbWV0ZXJpYyBhbmQgbm9uLXBhcmFtZXRyaWMgbWV0aG9kcyBiZWxvdywganVzdCB0byBiZSBzYWZlLgoKYGBge3J9CnBzb25fbW90aWxfdGlkeV9kZiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gYXZlcmFnZV92YWx1ZSkpICsKICBzdGF0X2RlbnNpdHkoYWVzKGNvbG91ciA9IGNlbGxMaW5lKSwKICAgICAgICAgICAgICAgZ2VvbSA9ICJsaW5lIiwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiUmVkcyIpICsKICBmYWNldF93cmFwKH4gc3VtbWFyeV9tZXRyaWMsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgoKIyMjIEV4cHJlc3Npb24gb3ZlcnZpZXcKCkJlZm9yZSBhbmFseXppbmcgdGhlIGV4cHJlc3Npb24gZGF0YSwgSSdkIGxpa2UgdG8gYXBwbHkgc29tZSBiYXNpYyB0cmFuc2Zvcm1hdGlvbnM6IGxvZy1ub3JtYWxpemluZyB0aGUgVFBNIGV4cHJlc3Npb24gdmFsdWVzIGFuZCByZW1vdmluZy9maWx0ZXJpbmcgbG93LCByYXJlbHksIG9yICJmbGF0bHkiICh+IGxvdyB2YXJpYW5jZSkgZXhwcmVzc2VkIGdlbmVzLiAKCmBgYHtyfQpleHByX2ZpbHRlcl9kZiA8LSBwc29uX2V4cHJfdHBtX2RmICU+JSAKICBnYXRoZXIoc2FtcGxlLCB0cG0sIC1nZW5lX2lkKSAlPiUgCiAgbXV0YXRlKGxvZ190cG0gPSBsb2codHBtICsgMSkpICU+JSAKICBncm91cF9ieShnZW5lX2lkKSAlPiUgCiAgc3VtbWFyaXplKG5fZXhwciA9IHN1bShsb2dfdHBtID4gMCksIAogICAgICAgICAgICBhdmdfZXhwciA9IG1lYW4obG9nX3RwbSksCiAgICAgICAgICAgICMgZm9ybXVsYSBmb3IgfkNWIGZvciBsb2ctbm9ybWFsaXplZCBkYXRhLCBhY2NvcmRpbmcgdG8gV2lraXBlZGlhCiAgICAgICAgICAgIGN2X2V4cHIgPSBzcXJ0KGV4cChzZChsb2dfdHBtKSBeIDIpIC0gMSkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZShyYXJlID0gbl9leHByIDwgbWF4KG5fZXhwcikgLyAyLAogICAgICAgICBsb3cgPSBhdmdfZXhwciA8IDEsCiAgICAgICAgIGZsYXQgPSBjdl9leHByIDwgMSwKICAgICAgICAgcmFyZV9vcl9sb3dfb3JfZmxhdCA9IHJhcmUgfCBsb3cgfCBmbGF0KQoKZXhwcl9maWx0ZXJfZGYgJT4lIAogIGdncGxvdChhZXMoeCA9IG5fZXhwciwgeSA9IGF2Z19leHByKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IHJhcmVfb3JfbG93X29yX2ZsYXQpLCBhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX2NvbG9yYmxpbmQoKQpgYGAKCioqTm90ZToqKiBJJ2xsIHdyaXRlIGEgc2ltcGxlIGZ1bmN0aW9uIGZvciB0aGlzIHN0ZXAsIHNvIEkgY2FuIGFwcGx5IHRoZSBzYW1lIGZpbHRlcmluZyBmb3Igc3Vic2V0cyBvZiB0aGUgZGF0YS4KCmBgYHtyfQpsb2dfYW5kX2ZpbHRlcl9nZW5lcyA8LSBmdW5jdGlvbihleHByX3RwbV9tZWx0ZWRfZGYpIHsKICBrZWVwX2dlbmVzIDwtIGV4cHJfdHBtX21lbHRlZF9kZiAlPiUgCiAgICBtdXRhdGUobG9nX3RwbSA9IGxvZyh0cG0gKyAxKSkgJT4lIAogICAgZ3JvdXBfYnkoZ2VuZV9pZCkgJT4lIAogICAgc3VtbWFyaXplKG5fZXhwciA9IHN1bShsb2dfdHBtID4gMCksIAogICAgICAgICAgICAgIGF2Z19leHByID0gbWVhbihsb2dfdHBtKSwKICAgICAgICAgICAgICAjIGZvcm11bGEgZm9yIH5DViBmb3IgbG9nLW5vcm1hbGl6ZWQgZGF0YSwgYWNjb3JkaW5nIHRvIFdpa2lwZWRpYQogICAgICAgICAgICAgIGN2X2V4cHIgPSBzcXJ0KGV4cChzZChsb2dfdHBtKSBeIDIpIC0gMSkpICU+JSAKICAgIHVuZ3JvdXAoKSAlPiUgCiAgICBtdXRhdGUocmFyZSA9IG5fZXhwciA8IG1heChuX2V4cHIpIC8gMiwKICAgICAgICAgICBsb3cgPSBhdmdfZXhwciA8IDEsCiAgICAgICAgICAgZmxhdCA9IGN2X2V4cHIgPCAxLAogICAgICAgICAgIHJhcmVfb3JfbG93X29yX2ZsYXQgPSByYXJlIHwgbG93IHwgZmxhdCkgJT4lIAogICAgZmlsdGVyKCFyYXJlX29yX2xvd19vcl9mbGF0KSAlPiUgCiAgICBwbHVjaygiZ2VuZV9pZCIpCiAgICAKICAgIAogICAgZXhwcl90cG1fbWVsdGVkX2RmICU+JSAKICAgICAgZmlsdGVyKGdlbmVfaWQgJWluJSBrZWVwX2dlbmVzKSAlPiUgCiAgICAgIG11dGF0ZShsb2d0cG0gPSBsb2codHBtICsgMSkpCn0KYGBgCgoKYGBge3J9CnBzb25fZXhwcl9sb2d0cG1fZGYgPC0gcHNvbl9leHByX3RwbV9kZiAlPiUgCiAgZ2F0aGVyKHNhbXBsZSwgdHBtLCAtZ2VuZV9pZCkgJT4lIAogIGxvZ19hbmRfZmlsdGVyX2dlbmVzKCkKYGBgCgpUaGUgc3Vic2V0dGVkIGV4cHJlc3Npb24gZGF0YWZyYW1lIGBwc29uX2V4cHJfbG9ndHBtX2RmYCBjb250YWlucyBgciBucm93KHBzb25fZXhwcl9sb2d0cG1fZGYpYCBnZW5lcy4gSSBjYW4gdGFrZSBhIHJhbmRvbSBzYW1wbGUgKDEwMCBnZW5lcykgYWdhaW4gdG8gY2hlY2sgZm9yIH5ub3JtYWxpdHkuCgpgYGB7cn0Kc2V0LnNlZWQoMCkKcHNvbl9leHByX2xvZ3RwbV9kZiAlPiUgCiAgZ3JvdXBfYnkoZ2VuZV9pZCkgJT4lIAogIG5lc3QoKSAlPiUgCiAgc2FtcGxlX24oMTAwKSAlPiUgCiAgdW5uZXN0KCkgJT4lIAogIGdncGxvdChhZXMoeCA9IGxvZ3RwbSkpICsKICBzdGF0X2RlbnNpdHkoYWVzKGdyb3VwID0gZ2VuZV9pZCksIAogICAgICAgICAgICAgICBnZW9tID0gImxpbmUiLCBwb3NpdGlvbiA9ICJpZGVudGl0eSIsIGFscGhhID0gMC4yKQpgYGAKCkxvb2tzIGxpa2UgYSBidW5jaCBvZiBHdWFzc2lhbi1pc2ggY3VydmVzIHRvIG1lLCBzbyB0aGF0J3MgZW5jb3VyYWdpbmcuCgotLS0KCiMjIE1vdGlsaXR5LWV4cHJlc3Npb24gY29ycmVsYXRpb24KCkJlbG93LCBJJ2xsIHRha2UgYSBsb29rIGF0IGNvcnJlbGF0aW9uIGJldHdlZW4gZ2VuZSBleHByZXNzaW9uIGFuZCBtb3RpbGl0eSBtZXRyaWNzIGluIFBTLU9OIGNlbGwgbGluZXMgZnJvbSBhIGZldyBkaWZmZXJlbnQgcGVyc3BlY3RpdmVzOgoKMS4gQWNyb3NzIGFsbCBjZWxsIGxpbmVzIGFuZCBjYW5jZXIgdHlwZXMgKGRpYWdub3NlcykKMi4gQWNyb3NzIGFsbCBjZWxsIGxpbmVzIHdpdGhpbiBlYWNoIGNhbmNlciB0eXBlCjMuIEFjcm9zcyBlYWNoIGJyZWFzdCBjYW5jZXIgY2VsbCBsaW5lCgojIyMgQWNyb3NzIGFsbCBjZWxsIGxpbmVzCgpUbyBlYXNlIHNvbWUgb2YgdGhlIG9wZXJhdGlvbnMgYmVsb3csIEknbGwgYnVpbGQgYSBuZXN0ZWQgZGF0YWZyYW1lIHdoZXJlLCBmb3IgZWFjaCBnZW5lIGFuZCBzdW1tYXJ5IG1ldHJpYywgdGhlIGBkYXRhYCBjb2x1bW4gc3RvcmVzIGEgZGF0YWZyYW1lIHdpdGggZXhwcmVzc2lvbiB2YWx1ZXMgYW5kIGF2ZXJhZ2UgbW90aWxpdHkgdmFsdWVzIGFjcm9zcyBhbGwgY2VsbCBsaW5lcyBhbmQgY29uZGl0aW9ucy4KCmBgYHtyfQpuZXN0X2V4cHJfbW90aWxfZGF0YSA8LSBmdW5jdGlvbihleHByX2xvZ3RwbV9kZiwgZmFjZXQgPSBOVUxMKSB7CiAgZ3JvdXB2YXJzIDwtIGMoImdlbmVfaWQiLCAic3VtbWFyeV9tZXRyaWMiKQogIGpvaW52YXJzIDwtIGMoInNhbXBsZSIpCiAgaWYgKCFpcy5udWxsKGZhY2V0KSkgewogICAgZ3JvdXB2YXJzIDwtIGMoZ3JvdXB2YXJzLCBmYWNldCkKICAgIGpvaW52YXJzIDwtIGMoam9pbnZhcnMsIGZhY2V0KQogIH0gZWxzZSB7CiAgICBmYWNldCA8LSAiIgogIH0KICBleHByX2xvZ3RwbV9kZiAlPiUgCiAgICBsZWZ0X2pvaW4oCiAgICAgIHBzb25fbW90aWxfdGlkeV9kZiAlPiUgCiAgICAgICAgZmlsdGVyKCEoc3VtbWFyeV9tZXRyaWMgJWluJSAiZW5kX3RvX2VuZF9kaXN0YW5jZV91bSIpKSAlPiUKICAgICAgICBzZWxlY3Qoc2FtcGxlLCBleHBlcmltZW50YWxDb25kaXRpb24sIG9uZV9vZihmYWNldCksCiAgICAgICAgICAgICAgIHN1bW1hcnlfbWV0cmljLCBhdmVyYWdlX3ZhbHVlX3NjYWxlZCksCiAgICAgIGJ5ID0gam9pbnZhcnMKICAgICkgJT4lIAogICAgZmlsdGVyKCFpcy5uYShleHBlcmltZW50YWxDb25kaXRpb24pKSAlPiUKICAgIHNlbGVjdChnZW5lX2lkLCBsb2d0cG0sIHN1bW1hcnlfbWV0cmljLCBhdmVyYWdlX3ZhbHVlX3NjYWxlZCwKICAgICAgICAgICBleHBlcmltZW50YWxDb25kaXRpb24sIG9uZV9vZihmYWNldCkpICU+JQogICAgZ3JvdXBfYnkoLmRvdHMgPSBncm91cHZhcnMpICU+JQogICAgbmVzdCgpICU+JQogICAgdW5ncm91cCgpCn0KCiMgYnVpbGQgbWFzdGVyIGRhdGFmcmFtZSBmb3IgaW5zcGVjdGluZyBleHByZXNzaW9uIH4gbW90aWxpdHkgdHJlbmRzCnBzb25fZXhwcl9tb3RpbF9kZiA8LSBuZXN0X2V4cHJfbW90aWxfZGF0YShwc29uX2V4cHJfbG9ndHBtX2RmKQpgYGAKClVzaW5nIHRoZSBgcHNvbl9leHByX21vdGlsX2RmYCAibWFzdGVyIiBkYXRhZnJhbWUsIEkgY2FuIGl0ZXJhdGUgYWNyb3NzIGFsbCBnZW5lcyB0byBjb21wdXRlIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGV4cHJlc3Npb24gYW5kIG1vdGlsaXR5IHZhbHVlcyBhY3Jvc3MgY29uZGl0aW9ucy4gVG8gY2xhcmlmeSB3aGF0IHRoaXMgbG9va3MgbGlrZSBmb3IgYSBzaW5nbGUgZ2VuZToKCmBgYHtyfQpwMSA8LSBwc29uX2V4cHJfbW90aWxfZGYgJT4lIAogIHNsaWNlKDEpICU+JSAKICB1bm5lc3QoZGF0YSkgJT4lIAogIGdncGxvdChhZXMoeCA9IGxvZ3RwbSwgeSA9IGF2ZXJhZ2VfdmFsdWVfc2NhbGVkKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICB5bGFiKCJzcGVlZF91bV9ociIpCnAxIDwtIGdnTWFyZ2luYWwocDEpCmBgYAoKYGBge3J9CmdyaWQ6OmdyaWQubmV3cGFnZSgpCmdyaWQ6OmdyaWQuZHJhdyhwMSkKYGBgCgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmNhbGNfZ2VuZXdpc2VfY29yciA8LSBmdW5jdGlvbihleHByX21vdGlsX2RmKSB7CiAgZXhwcl9tb3RpbF9jb3JyX2RmIDwtIGV4cHJfbW90aWxfZGYgJT4lIAogICAgbXV0YXRlKAogICAgICBwZWFyc29uID0gbWFwKAogICAgICAgIGRhdGEsIAogICAgICAgIH4gY29yLnRlc3QoLiRsb2d0cG0sIC4kYXZlcmFnZV92YWx1ZV9zY2FsZWQsIAogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInBlYXJzb24iKSAlPiUgCiAgICAgICAgICB0aWR5KCkgJT4lIAogICAgICAgICAgZHBseXI6OnJlbmFtZV9hbGwoLmZ1bnMgPSB+c3RyX2MoInBlYXJzb25fIiwgLikpCiAgICAgICksCiAgICAgIHNwZWFybWFuID0gbWFwKAogICAgICAgIGRhdGEsIAogICAgICAgIH4gY29yLnRlc3QoLiRsb2d0cG0sIC4kYXZlcmFnZV92YWx1ZV9zY2FsZWQsIAogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInNwZWFybWFuIiwgZXhhY3QgPSBGQUxTRSkgJT4lIAogICAgICAgICAgdGlkeSgpICU+JSAKICAgICAgICAgIGRwbHlyOjpyZW5hbWVfYWxsKC5mdW5zID0gfnN0cl9jKCJzcGVhcm1hbl8iLCAuKSkKICAgICAgKQogICAgKSAlPiUgCiAgICBzZWxlY3QoLWRhdGEpCiAgCiAgZ3JvdXB2YXJzIDwtIHNldGRpZmYoCiAgICBuYW1lcyhleHByX21vdGlsX2NvcnJfZGYpLCAKICAgIGMoImdlbmVfaWQiLCAicGVhcnNvbiIsICJzcGVhcm1hbiIpCiAgKQogIHByaW50KGdyb3VwdmFycykKICAjICMgdW5uZXN0IGFuZCBhZGp1c3QgZm9yIG11bHRpcGxlIHRlc3RpbmcKICBleHByX21vdGlsX2NvcnJfZGYgJT4lCiAgICB1bm5lc3QocGVhcnNvbikgJT4lCiAgICB1bm5lc3Qoc3BlYXJtYW4pICU+JSAKICAgIGdyb3VwX2J5KC5kb3RzID0gZ3JvdXB2YXJzKSAlPiUKICAgIG11dGF0ZV9hdCgudmFycyA9IHZhcnMobWF0Y2hlcygiLipfcC52YWx1ZSIpKSwKICAgICAgICAgICAgICAuZnVucyA9IGZ1bnMoYWRqID0gcC5hZGp1c3QoLiwgbWV0aG9kID0gIkJIIikpKSAlPiUKICAgIHVuZ3JvdXAoKQp9CgojIGNvbXB1dGUgY29ycmVsYXRpb24gYWNyb3NzIGNlbGwgbGluZXMgYW5kIGNvbmRpdGlvbnMgZm9yIGFsbCBnZW5lcwpleHByX21vdGlsX2NvcnJfYWxsX2RmIDwtIGNhbGNfZ2VuZXdpc2VfY29ycihwc29uX2V4cHJfbW90aWxfZGYpCmBgYAoKSWRlYWxseSwgSSdkIGxpa2UgdG8gZmluZCBnZW5lcyB0aGF0IGFyZSBzaWduaWZpY2FudGx5IGNvcnJlbGF0ZWQgd2l0aCBtb3RpbGl0eSBhZnRlciBjb3JyZWN0aW5nIGZvciBtdWx0aXBsZSBoeXBvdGhlc2lzIHRlc3RpbmcgKGkuZS4sIEJlbmphbWluaS1Ib2NoYmVyZykuIEZpbHRlcmluZyBmb3IgZ2VuZXMgd2l0aCBhZGp1c3RlZCBwLXZhbHVlIDwgMC4wNSwgSSBnZXQuLi4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpleHByX21vdGlsX2NvcnJfYWxsX3NpZ19kZiA8LSBleHByX21vdGlsX2NvcnJfYWxsX2RmICU+JSAKICBmaWx0ZXJfYXQoLnZhcnMgPSB2YXJzKG1hdGNoZXMoIi4qX3AudmFsdWVfYWRqIikpLAogICAgICAgICAgICAudmFyc19wcmVkaWNhdGUgPSBhbnlfdmFycyguIDwgMC4wNSkpCmV4cHJfbW90aWxfY29ycl9hbGxfc2lnX2RmCmBgYAoKLi4ubm90aGluZyBzaWduaWZpY2FudC4gTGV0J3MgdHJ5IHRvIGxvb2sgYXQgdW5hZGp1c3RlZCBwLXZhbHVlcy4KCmBgYHtyfQpleHByX21vdGlsX2NvcnJfYWxsX3NpZ19kZiA8LSBleHByX21vdGlsX2NvcnJfYWxsX2RmICU+JSAKICBmaWx0ZXJfYXQoLnZhcnMgPSB2YXJzKG1hdGNoZXMoIi4qX3AudmFsdWUiKSksCiAgICAgICAgICAgIC52YXJzX3ByZWRpY2F0ZSA9IGFueV92YXJzKC4gPCAwLjA1KSkgJT4lIAogIHNlbGVjdChnZW5lX2lkLCBzdW1tYXJ5X21ldHJpYywgCiAgICAgICAgIHBlYXJzb24gPSBwZWFyc29uX2VzdGltYXRlLCBzcGVhcm1hbiA9IHNwZWFybWFuX2VzdGltYXRlKSAlPiUgCiAgZ2F0aGVyKGNvcnJfdHlwZSwgY29yciwgcGVhcnNvbiwgc3BlYXJtYW4pICU+JSAKICBsZWZ0X2pvaW4oc2VsZWN0KGdlbmVfZGYsIGdlbmVfbmFtZSA9IHN5bWJvbCwgZXZlcnl0aGluZygpKSwgYnkgPSAiZ2VuZV9pZCIpCmV4cHJfbW90aWxfY29ycl9hbGxfc2lnX2RmCmBgYAoKV2l0aCB0aGlzIHZpZXcsIHdlIGdldCBgciBuX2Rpc3RpbmN0KGV4cHJfbW90aWxfY29ycl9hbGxfc2lnX2RmJGdlbmVfaWQpYCBnZW5lcyB0aGF0IGFyZSBjb3JyZWxhdGVkIHdpdGggKmVpdGhlciogc3BlZWQgb3IgZGlzdGFuY2UgYmFzZWQgb24gKmVpdGhlciogUGVhcnNvbiBvciBTcGVhcm1hbiBjb3JyZWxhdGlvbi4gVGFraW5nIGEgbG9vayBhdCB0aGVzZSBnZW5lczoKCmBgYHtyfQpleHByX21vdGlsX2NvcnJfYWxsX3NpZ19kZiAlPiUgCiAgbXV0YXRlKG5lZyA9IGNvcnIgPCAwLAogICAgICAgICBnZW5lX25hbWUgPSBpZl9lbHNlKGlzLm5hKGdlbmVfbmFtZSksIGdlbmVfaWQsIGdlbmVfbmFtZSkpICU+JSAKICBhcnJhbmdlKGNvcnIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjb3JyX3R5cGUsIHkgPSBjb3JyKSkgKwogIGdlb21fcG9pbnQoYWVzKGZpbGwgPSBjb3JyKSwgc2hhcGUgPSAyMSwgY29sb3VyID0gInNsYXRlZ3JheSIsIHNpemUgPSAzKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbCA9IGdlbmVfbmFtZSksIHNpemUgPSAzKSArCiAgeGxhYigiIikgKwogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiUHVPciIsIGxpbWl0cyA9IGMoLTEsIDEpKSArCiAgZmFjZXRfZ3JpZChuZWcgfiBzdW1tYXJ5X21ldHJpYywgc2NhbGVzID0gImZyZWVfeSIpICsKICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHRpdGxlID0gIkdlbmVzIHNpZ25pZmljYW50bHkqIGNvcnJlbGF0ZWQgd2l0aCBtb3RpbGl0eSIsCiAgICAgICBzdWJ0aXRsZSA9ICIoYWNyb3NzIGFsbCBjZWxsIGxpbmVzIGFuZCBjYW5jZXIgdHlwZXMpIiwKICAgICAgIGNhcHRpb24gPSAiKiBwLXZhbHVlIDwgMC4wNSAobm90IGFkanVzdGVkIGZvciBtdWx0aXBsZSB0ZXN0aW5nKSIpCmBgYAoKIyMjIEZvciBlYWNoIGNhbmNlciB0eXBlCgpUYWtpbmcgYSBzbGlnaHRseSBtb3JlIGdyYW51bGFyIGFwcHJvYWNoLCBJJ2xsIGxvb2sgYXQgZ2VuZS1tb3RpbGl0eSBjb3JyZWxhdGlvbiBhY3Jvc3MgY2VsbCBsaW5lcyBmb3IgZWFjaCBjYW5jZXIgdHlwZS4KCmBgYHtyfQojIGJ1aWxkIG1hc3RlciBkYXRhZnJhbWUgZm9yIGluc3BlY3RpbmcgZXhwcmVzc2lvbiB+IG1vdGlsaXR5IHRyZW5kcwpwc29uX2V4cHJfbW90aWxfZGlhZ19kZiA8LSBwc29uX2V4cHJfdHBtX2RmICU+JQogIGdhdGhlcihzYW1wbGUsIHRwbSwgLWdlbmVfaWQpICU+JSAKICBsZWZ0X2pvaW4oc2VsZWN0KHNhbXBsZV9tYXBfZGYsIHNhbXBsZSwgZGlhZ25vc2lzKSwgYnkgPSAic2FtcGxlIikgJT4lIAogIGdyb3VwX2J5KGRpYWdub3NpcykgJT4lIAogIG5lc3QoKSAlPiUgCiAgbXV0YXRlKGRhdGEgPSBtYXAoZGF0YSwgbG9nX2FuZF9maWx0ZXJfZ2VuZXMpKSAlPiUgCiAgdW5uZXN0KGRhdGEpICU+JSAKICBuZXN0X2V4cHJfbW90aWxfZGF0YShmYWNldCA9ICJkaWFnbm9zaXMiKQpgYGAKClRoZSBwcmluY2lwbGUgaGVyZSBpcyBwcmV0dHkgbXVjaCB0aGUgc2FtZSwgYnV0IHdpdGggZmV3ZXIgcG9pbnRzIGluIGVhY2ggb2YgdGhlIGRpc3RyaWJ1dGlvbnMgdGhhdCBJJ20gY29tcGFyaW5nIChhdCB0aGUgc2FtZSB0aW1lLCB0aGVyZSdzIGxlc3Mgbm9pc2UgZHVlIHRvIGNlbGwgdHlwZSBzcGVjaWZpYyBlZmZlY3RzLCBzbyB3ZSBjYW4gZ2VuZXJhbGx5IGV4cGVjdCB0byBzZWUgY29ycmVsYXRpb24gdmFsdWVzIHdpdGggZ3JlYXRlciBtYWduaXR1ZGUuKQoKYGBge3J9CnAyIDwtIHBzb25fZXhwcl9tb3RpbF9kaWFnX2RmICU+JSAKICBzbGljZSgxKSAlPiUgCiAgdW5uZXN0KGRhdGEpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2d0cG0sIHkgPSBhdmVyYWdlX3ZhbHVlX3NjYWxlZCkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArCiAgeWxhYigic3BlZWRfdW1faHIiKQpwMiA8LSBnZ01hcmdpbmFsKHAyKQpgYGAKCmBgYHtyfQpncmlkOjpncmlkLm5ld3BhZ2UoKQpncmlkOjpncmlkLmRyYXcocDIpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiMgY29tcHV0ZSBjb3JyZWxhdGlvbiBhY3Jvc3MgY2VsbCBsaW5lcyBhbmQgY29uZGl0aW9ucyBmb3IgYWxsIGdlbmVzIGFuZCBhbGwKIyBjYW5jZXIgdHlwZXMKZXhwcl9tb3RpbF9jb3JyX2RpYWdfZGYgPC0gY2FsY19nZW5ld2lzZV9jb3JyKHBzb25fZXhwcl9tb3RpbF9kaWFnX2RmKQpgYGAKCk5vdywgZ2VuZXMgdGhhdCBhcmUgc2lnbmlmaWNhbnRseSBjb3JyZWxhdGVkIHdpdGggbW90aWxpdHkgYWZ0ZXIgY29ycmVjdGluZyBmb3IgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nIChpLmUuLCBCSC1hZGp1c3RlZCBwLXZhbHVlKToKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpleHByX21vdGlsX2NvcnJfZGlhZ19zaWdfZGYgPC0gZXhwcl9tb3RpbF9jb3JyX2RpYWdfZGYgJT4lIAogIGZpbHRlcl9hdCgudmFycyA9IHZhcnMobWF0Y2hlcygiLipfcC52YWx1ZV9hZGoiKSksCiAgICAgICAgICAgIC52YXJzX3ByZWRpY2F0ZSA9IGFueV92YXJzKC4gPCAwLjA1KSkgJT4lIAogIHNlbGVjdChnZW5lX2lkLCBzdW1tYXJ5X21ldHJpYywgZGlhZ25vc2lzLAogICAgICAgICBwZWFyc29uID0gcGVhcnNvbl9lc3RpbWF0ZSwgc3BlYXJtYW4gPSBzcGVhcm1hbl9lc3RpbWF0ZSkgJT4lIAogIGdhdGhlcihjb3JyX3R5cGUsIGNvcnIsIHBlYXJzb24sIHNwZWFybWFuKSAlPiUgCiAgbGVmdF9qb2luKHNlbGVjdChnZW5lX2RmLCBnZW5lX25hbWUgPSBzeW1ib2wsIGV2ZXJ5dGhpbmcoKSksIGJ5ID0gImdlbmVfaWQiKQpleHByX21vdGlsX2NvcnJfZGlhZ19zaWdfZGYKYGBgCgpBbiBpbXByZXNzaXZlIGByIG5fZGlzdGluY3QoZXhwcl9tb3RpbF9jb3JyX2RpYWdfc2lnX2RmJGdlbmVfaWQpYCBnZW5lKHMpIGNvcnJlbGF0ZWQgd2l0aCAqZWl0aGVyKiBzcGVlZCBvciBkaXN0YW5jZSBiYXNlZCBvbiAqZWl0aGVyKiBQZWFyc29uIG9yIFNwZWFybWFuIGNvcnJlbGF0aW9uIHdpdGhpbiAqKiphbnkqKiogY2FuY2VyIHR5cGUuIE5vYm9keSBsaWtlcyBtdWx0aXBsZSB0ZXN0aW5nIGNvcnJlY3Rpb24gYW55d2F5IOKAlCBsZXQncyBza2lwIGl0LgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmV4cHJfbW90aWxfY29ycl9kaWFnX3NpZ19kZiA8LSBleHByX21vdGlsX2NvcnJfZGlhZ19kZiAlPiUgCiAgZmlsdGVyX2F0KC52YXJzID0gdmFycyhtYXRjaGVzKCIuKl9wLnZhbHVlIikpLAogICAgICAgICAgICAudmFyc19wcmVkaWNhdGUgPSBhbnlfdmFycyguIDwgMC4wMSkpICU+JSAKICBzZWxlY3QoZ2VuZV9pZCwgc3VtbWFyeV9tZXRyaWMsIGRpYWdub3NpcywKICAgICAgICAgcGVhcnNvbiA9IHBlYXJzb25fZXN0aW1hdGUsIHNwZWFybWFuID0gc3BlYXJtYW5fZXN0aW1hdGUpICU+JSAKICBnYXRoZXIoY29ycl90eXBlLCBjb3JyLCBwZWFyc29uLCBzcGVhcm1hbikgJT4lIAogIGxlZnRfam9pbihzZWxlY3QoZ2VuZV9kZiwgZ2VuZV9uYW1lID0gc3ltYm9sLCBldmVyeXRoaW5nKCkpLCBieSA9ICJnZW5lX2lkIikKZXhwcl9tb3RpbF9jb3JyX2RpYWdfc2lnX2RmCmBgYAoKYGBge3J9CmV4cHJfbW90aWxfY29ycl9kaWFnX3NpZ19kZiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZ2VuZV9uYW1lLCB5ID0gY29ycl90eXBlKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbCA9IGNvcnIpLCBjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC41KSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJQdU9yIiwgbGltaXRzID0gYygtMSwgMSkpICsKICB5bGFiKCIiKSArCiAgeGxhYigiIikgKwogIGZhY2V0X2dyaWQoZGlhZ25vc2lzIH4gc3VtbWFyeV9tZXRyaWMsIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWVfeCIsIHNwYWNlID0gImZyZWVfeCIsIGRyb3AgPSBUUlVFKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCBzaXplID0gNyksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA2KSkgKwogIGxhYnModGl0bGUgPSAiR2VuZXMgc2lnbmlmaWNhbnRseSogY29ycmVsYXRlZCB3aXRoIG1vdGlsaXR5IiwKICAgICAgIHN1YnRpdGxlID0gIihhY3Jvc3MgYWxsIGNlbGwgbGluZXMgd2l0aGluIGVhY2ggY2FuY2VyIHR5cGUpIiwKICAgICAgIGNhcHRpb24gPSAiKiBwLXZhbHVlIDwgMC4wMSAobm90IGFkanVzdGVkIGZvciBtdWx0aXBsZSB0ZXN0aW5nKSIpCmBgYAoKIyMjIyBBY3Jvc3MgYnJlYXN0IGNhbmNlciBjZWxsIGxpbmVzCgpUaGVzZSBhcmUgdGhlIHNhbWUgcmVzdWx0cyBhcyBhYm92ZSwgYnV0IGZvY3VzaW5nIGluIG9uIHRoZSBnZW5lcyB0aGF0IGFwcGVhciB0byBiZSBjb3JyZWxhdGVkIHdpdGggbW90aWxpdHkgYWNyb3NzIGJyZWFzdCBjYW5jZXIgY2VsbCBsaW5lcy4KCmBgYHtyfQpleHByX21vdGlsX2NvcnJfZGlhZ19icmNhX3NpZ19kZiA8LSBleHByX21vdGlsX2NvcnJfZGlhZ19kZiAlPiUgCiAgZmlsdGVyKGRpYWdub3NpcyA9PSAiQnJlYXN0IENhbmNlciIpICU+JSAKICBmaWx0ZXJfYXQoLnZhcnMgPSB2YXJzKG1hdGNoZXMoIi4qX3AudmFsdWUiKSksCiAgICAgICAgICAgIC52YXJzX3ByZWRpY2F0ZSA9IGFueV92YXJzKC4gPCAwLjA1KSkgJT4lIAogIHNlbGVjdChnZW5lX2lkLCBzdW1tYXJ5X21ldHJpYywgZGlhZ25vc2lzLAogICAgICAgICBwZWFyc29uID0gcGVhcnNvbl9lc3RpbWF0ZSwgc3BlYXJtYW4gPSBzcGVhcm1hbl9lc3RpbWF0ZSkgJT4lCiAgZ2F0aGVyKGNvcnJfdHlwZSwgY29yciwgcGVhcnNvbiwgc3BlYXJtYW4pICU+JQogIGxlZnRfam9pbihzZWxlY3QoZ2VuZV9kZiwgZ2VuZV9uYW1lID0gc3ltYm9sLCBldmVyeXRoaW5nKCkpLCBieSA9ICJnZW5lX2lkIikKZXhwcl9tb3RpbF9jb3JyX2RpYWdfYnJjYV9zaWdfZGYKYGBgCgpgYGB7cn0KZXhwcl9tb3RpbF9jb3JyX2RpYWdfYnJjYV9zaWdfZGYgJT4lIAogIG11dGF0ZShuZWcgPSBjb3JyIDwgMCwKICAgICAgICAgZ2VuZV9uYW1lID0gaWZfZWxzZShpcy5uYShnZW5lX25hbWUpLCBnZW5lX2lkLCBnZW5lX25hbWUpKSAlPiUgCiAgYXJyYW5nZShjb3JyKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gY29ycl90eXBlLCB5ID0gY29ycikpICsKICBnZW9tX3BvaW50KGFlcyhmaWxsID0gY29yciksIHNoYXBlID0gMjEsIGNvbG91ciA9ICJzbGF0ZWdyYXkiLCBzaXplID0gMykgKwogIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSBnZW5lX25hbWUpLCBzaXplID0gMykgKwogIHhsYWIoIiIpICsKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlB1T3IiLCBsaW1pdHMgPSBjKC0xLCAxKSkgKwogIGZhY2V0X2dyaWQobmVnIH4gc3VtbWFyeV9tZXRyaWMsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgbGFicyh0aXRsZSA9ICJHZW5lcyBzaWduaWZpY2FudGx5KiBjb3JyZWxhdGVkIHdpdGggbW90aWxpdHkiLAogICAgICAgc3VidGl0bGUgPSAiKGFjcm9zcyBhbGwgYnJlYXN0IGNhbmNlciBjZWxsIGxpbmVzKSIsCiAgICAgICBjYXB0aW9uID0gIiogcC12YWx1ZSA8IDAuMDUgKG5vdCBhZGp1c3RlZCBmb3IgbXVsdGlwbGUgdGVzdGluZykiKQpgYGAKCgojIyMgRm9yIGVhY2ggYnJlYXN0IGNhbmNlciBjZWxsIGxpbmUKClRha2luZyBhIHNsaWdodGx5IG1vcmUgZ3JhbnVsYXIgYXBwcm9hY2gsIEknbGwgbG9vayBhdCBnZW5lLW1vdGlsaXR5IGNvcnJlbGF0aW9uIGFjcm9zcyBjZWxsIGxpbmVzIGZvciBlYWNoIGNhbmNlciB0eXBlLgoKYGBge3J9CmJyY2FfZXhwcl9tb3RpbF9jZWxsbGluZV9kZiA8LSBwc29uX2V4cHJfdHBtX2RmICU+JQogIGdhdGhlcihzYW1wbGUsIHRwbSwgLWdlbmVfaWQpICU+JSAKICBsZWZ0X2pvaW4oc2VsZWN0KHNhbXBsZV9tYXBfZGYsIHNhbXBsZSwgZGlhZ25vc2lzLCBjZWxsTGluZSksIAogICAgICAgICAgICBieSA9ICJzYW1wbGUiKSAlPiUgCiAgZmlsdGVyKGRpYWdub3NpcyA9PSAiQnJlYXN0IENhbmNlciIpICU+JQogIGdyb3VwX2J5KGNlbGxMaW5lKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKGRhdGEgPSBtYXAoZGF0YSwgbG9nX2FuZF9maWx0ZXJfZ2VuZXMpKSAlPiUKICB1bm5lc3QoZGF0YSkgJT4lCiAgbmVzdF9leHByX21vdGlsX2RhdGEoZmFjZXQgPSAiY2VsbExpbmUiKQpgYGAKCkV2ZW4gZmV3ZXIgcG9pbnRzIGluIGVhY2ggb2YgdGhlIGRpc3RyaWJ1dGlvbnMgdGhhdCBJJ20gY29tcGFyaW5nICgqKjEqKiBwZXIgZXhwZXJpbWVudGFsIGNvbmRpdGlvbiBpbiBib3RoIHRoZSBtb3RpbGl0eSBhbmQgZXhwcmVzc2lvbiBkYXRhKSDigJQgaGlnaGVyIGxpa2VsaXlob29kIG9mIGZpbmRpbmcgc3Ryb25nZXIgY29ycmVsYXRpb24sIGJ1dCBkZWZpbml0ZWx5IGxlc3Mgc3RhdGlzdGljYWwgcG93ZXIuCgpgYGB7cn0KcDMgPC0gYnJjYV9leHByX21vdGlsX2NlbGxsaW5lX2RmICU+JSAKICBzbGljZSgxKSAlPiUgCiAgdW5uZXN0KGRhdGEpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2d0cG0sIHkgPSBhdmVyYWdlX3ZhbHVlX3NjYWxlZCkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArCiAgeWxhYigic3BlZWRfdW1faHIiKQpwMyA8LSBnZ01hcmdpbmFsKHAzKQpgYGAKCmBgYHtyfQpncmlkOjpncmlkLm5ld3BhZ2UoKQpncmlkOjpncmlkLmRyYXcocDMpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiMgY29tcHV0ZSBjb3JyZWxhdGlvbiBhY3Jvc3MgY2VsbCBsaW5lcyBhbmQgY29uZGl0aW9ucyBmb3IgYWxsIGdlbmVzIGFuZCBhbGwKIyBjYW5jZXIgdHlwZXMKYnJjYV9leHByX21vdGlsX2NvcnJfY2VsbGxpbmVfZGYgPC0gY2FsY19nZW5ld2lzZV9jb3JyKGJyY2FfZXhwcl9tb3RpbF9jZWxsbGluZV9kZikKYGBgCgpBbnkgZ2VuZXMgc2lnbmlmaWNhbnRseSBjb3JyZWxhdGVkIHdpdGggbW90aWxpdHkgYWZ0ZXIgY29ycmVjdGluZyBmb3IgbXVsdGlwbGUgaHlwb3RoZXNpcyB0ZXN0aW5nIChCSC1hZGp1c3RlZCBwLXZhbHVlKT8KCmBgYHtyfQpicmNhX2V4cHJfbW90aWxfY29ycl9jZWxsbGluZV9zaWdfZGYgPC0gYnJjYV9leHByX21vdGlsX2NvcnJfY2VsbGxpbmVfZGYgJT4lIAogIGZpbHRlcl9hdCgudmFycyA9IHZhcnMobWF0Y2hlcygiLipfcC52YWx1ZV9hZGoiKSksCiAgICAgICAgICAgIC52YXJzX3ByZWRpY2F0ZSA9IGFueV92YXJzKC4gPCAwLjA1KSkgJT4lIAogIHNlbGVjdChnZW5lX2lkLCBzdW1tYXJ5X21ldHJpYywgY2VsbExpbmUsCiAgICAgICAgIHBlYXJzb24gPSBwZWFyc29uX2VzdGltYXRlLCBzcGVhcm1hbiA9IHNwZWFybWFuX2VzdGltYXRlKSAlPiUKICBnYXRoZXIoY29ycl90eXBlLCBjb3JyLCBwZWFyc29uLCBzcGVhcm1hbikgJT4lCiAgbGVmdF9qb2luKHNlbGVjdChnZW5lX2RmLCBnZW5lX25hbWUgPSBzeW1ib2wsIGV2ZXJ5dGhpbmcoKSksIGJ5ID0gImdlbmVfaWQiKQpicmNhX2V4cHJfbW90aWxfY29ycl9jZWxsbGluZV9zaWdfZGYKYGBgCgpOb3BlLiBSZWxheGluZyB0aGUgdGhyZXNob2xkIGEgYml0IGZ1cnRoZXIgbm93ICh1bmFkanVzdGVkIHAtdmFsdWUgPCAwLjA1KToKCmBgYHtyfQpicmNhX2V4cHJfbW90aWxfY29ycl9jZWxsbGluZV9zaWdfZGYgPC0gYnJjYV9leHByX21vdGlsX2NvcnJfY2VsbGxpbmVfZGYgJT4lIAogIGZpbHRlcl9hdCgudmFycyA9IHZhcnMobWF0Y2hlcygiLipfcC52YWx1ZSIpKSwKICAgICAgICAgICAgLnZhcnNfcHJlZGljYXRlID0gYW55X3ZhcnMoLiA8IDAuMDUpKSAlPiUgCiAgc2VsZWN0KGdlbmVfaWQsIHN1bW1hcnlfbWV0cmljLCBjZWxsTGluZSwKICAgICAgICAgcGVhcnNvbiA9IHBlYXJzb25fZXN0aW1hdGUsIHNwZWFybWFuID0gc3BlYXJtYW5fZXN0aW1hdGUpICU+JQogIGdhdGhlcihjb3JyX3R5cGUsIGNvcnIsIHBlYXJzb24sIHNwZWFybWFuKSAlPiUKICBsZWZ0X2pvaW4oc2VsZWN0KGdlbmVfZGYsIGdlbmVfbmFtZSA9IHN5bWJvbCwgZXZlcnl0aGluZygpKSwgYnkgPSAiZ2VuZV9pZCIpCmJyY2FfZXhwcl9tb3RpbF9jb3JyX2NlbGxsaW5lX3NpZ19kZgpgYGAKCmBgYHtyfQpicmNhX2V4cHJfbW90aWxfY29ycl9jZWxsbGluZV9zaWdfZGYgJT4lIAogIG11dGF0ZShuZWcgPSBjb3JyIDwgMCwKICAgICAgICAgZ2VuZV9uYW1lID0gaWZfZWxzZShpcy5uYShnZW5lX25hbWUpLCBnZW5lX2lkLCBnZW5lX25hbWUpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZ2VuZV9uYW1lLCB5ID0gY29ycl90eXBlKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbCA9IGNvcnIpLCBjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC41KSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJQdU9yIiwgbGltaXRzID0gYygtMSwgMSkpICsKICB4bGFiKCIiKSArCiAgeWxhYigiIikgKwogIGZhY2V0X2dyaWQoY2VsbExpbmUgfiBzdW1tYXJ5X21ldHJpYywgCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZV94Iiwgc3BhY2UgPSAiZnJlZV94IiwgZHJvcCA9IFRSVUUpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHNpemUgPSA4KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDYpKSArCiAgbGFicyh0aXRsZSA9ICJHZW5lcyBzaWduaWZpY2FudGx5KiBjb3JyZWxhdGVkIHdpdGggbW90aWxpdHkiLAogICAgICAgc3VidGl0bGUgPSAiKGZvciBlYWNoIGJyZWFzdCBjYW5jZXIgY2VsbCBsaW5lKSIsCiAgICAgICBjYXB0aW9uID0gIiogcC12YWx1ZSA8IDAuMDUgKG5vdCBhZGp1c3RlZCBmb3IgbXVsdGlwbGUgdGVzdGluZykiKQoKYGBgCgpOb3QgbXVjaCB0byBzZWUsIGJ1dCBtYXliZSB0aGVzZSBnZW5lcyBtZWFuIG1vcmUgdG8gb3RoZXJzLgoKLS0tCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKCg==